mirror of
https://github.com/grafana/grafana.git
synced 2025-02-25 18:55:37 -06:00
SAML: Configuration defaults, examples and dependencies (#17954)
* Add SAML configuration options * Add crewjam/saml as a depdency Needed as part of the enterprise SAML integration. * Vendor github.com/stretchr/testify/require The package require implements the same assertions as the `assert` package but stops test execution when a test fails.
This commit is contained in:
parent
48d5a1bcd3
commit
e6b8a1529b
@ -377,6 +377,19 @@ tls_client_key =
|
||||
tls_client_ca =
|
||||
send_client_credentials_via_post = false
|
||||
|
||||
#################################### SAML Auth ###########################
|
||||
[auth.saml] # Enterprise only
|
||||
enabled = false
|
||||
private_key =
|
||||
private_key_path =
|
||||
certificate =
|
||||
certificate_path =
|
||||
idp_metadata =
|
||||
idp_metadata_path =
|
||||
idp_metadata_url =
|
||||
max_issue_delay = 90s
|
||||
metadata_valid_duration = 48h
|
||||
|
||||
#################################### Basic Auth ##########################
|
||||
[auth.basic]
|
||||
enabled = true
|
||||
|
@ -333,6 +333,19 @@
|
||||
; This might be required if the OAuth provider is not RFC6749 compliant, only supporting credentials passed via POST payload
|
||||
;send_client_credentials_via_post = false
|
||||
|
||||
#################################### SAML Auth ###########################
|
||||
;[auth.saml] # Enterprise only
|
||||
;enabled = false
|
||||
;private_key =
|
||||
;private_key_path =
|
||||
;certificate =
|
||||
;certificate_path =
|
||||
;idp_metadata =
|
||||
;idp_metadata_path =
|
||||
;idp_metadata_url =
|
||||
;max_issue_delay = 90s
|
||||
;metadata_valid_duration = 48h
|
||||
|
||||
#################################### Grafana.com Auth ####################
|
||||
[auth.grafana_com]
|
||||
;enabled = false
|
||||
|
7
go.mod
7
go.mod
@ -7,12 +7,15 @@ require (
|
||||
github.com/Unknwon/com v0.0.0-20190214221849-2d12a219ccaf
|
||||
github.com/VividCortex/mysqlerr v0.0.0-20170204212430-6c6b55f8796f
|
||||
github.com/aws/aws-sdk-go v1.18.5
|
||||
github.com/beevik/etree v1.1.0 // indirect
|
||||
github.com/benbjohnson/clock v0.0.0-20161215174838-7dc76406b6d3
|
||||
github.com/bradfitz/gomemcache v0.0.0-20180710155616-bc664df96737
|
||||
github.com/codahale/hdrhistogram v0.0.0-20161010025455-3a0bb77429bd // indirect
|
||||
github.com/codegangsta/cli v1.20.0
|
||||
github.com/crewjam/saml v0.0.0-20190521120225-344d075952c9
|
||||
github.com/davecgh/go-spew v1.1.1
|
||||
github.com/denisenkom/go-mssqldb v0.0.0-20190315220205-a8ed825ac853
|
||||
github.com/dgrijalva/jwt-go v3.2.0+incompatible // indirect
|
||||
github.com/facebookgo/ensure v0.0.0-20160127193407-b4ab57deab51 // indirect
|
||||
github.com/facebookgo/inject v0.0.0-20180706035515-f23751cae28b
|
||||
github.com/facebookgo/stack v0.0.0-20160209184415-751773369052 // indirect
|
||||
@ -37,6 +40,7 @@ require (
|
||||
github.com/hashicorp/go-plugin v0.0.0-20190220160451-3f118e8ee104
|
||||
github.com/hashicorp/go-version v1.1.0
|
||||
github.com/inconshreveable/log15 v0.0.0-20180818164646-67afb5ed74ec
|
||||
github.com/jonboulle/clockwork v0.1.0 // indirect
|
||||
github.com/k0kubun/colorstring v0.0.0-20150214042306-9440f1994b88 // indirect
|
||||
github.com/klauspost/compress v1.4.1 // indirect
|
||||
github.com/klauspost/cpuid v1.2.0 // indirect
|
||||
@ -48,12 +52,13 @@ require (
|
||||
github.com/onsi/gomega v1.5.0 // indirect
|
||||
github.com/opentracing/opentracing-go v1.1.0
|
||||
github.com/patrickmn/go-cache v2.1.0+incompatible
|
||||
github.com/pkg/errors v0.8.1 // indirect
|
||||
github.com/pkg/errors v0.8.1
|
||||
github.com/prometheus/client_golang v0.9.2
|
||||
github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90
|
||||
github.com/prometheus/common v0.2.0
|
||||
github.com/rainycape/unidecode v0.0.0-20150907023854-cb7f23ec59be // indirect
|
||||
github.com/robfig/cron v0.0.0-20180505203441-b41be1df6967
|
||||
github.com/russellhaering/goxmldsig v0.0.0-20180430223755-7acd5e4a6ef7 // indirect
|
||||
github.com/sergi/go-diff v1.0.0 // indirect
|
||||
github.com/smartystreets/assertions v0.0.0-20190401211740-f487f9de1cd3 // indirect
|
||||
github.com/smartystreets/goconvey v0.0.0-20190330032615-68dc04aab96a
|
||||
|
11
go.sum
11
go.sum
@ -10,6 +10,8 @@ github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuy
|
||||
github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=
|
||||
github.com/aws/aws-sdk-go v1.18.5 h1:S6j4o4AoJpq98DRc7wQrQsPZg73NyntGtUj6K6NPnuY=
|
||||
github.com/aws/aws-sdk-go v1.18.5/go.mod h1:KmX6BPdI08NWTb3/sm4ZGu5ShLoqVDhKgpiN924inxo=
|
||||
github.com/beevik/etree v1.1.0 h1:T0xke/WvNtMoCqgzPhkX2r4rjY3GDZFi+FjpRZY2Jbs=
|
||||
github.com/beevik/etree v1.1.0/go.mod h1:r8Aw8JqVegEf0w2fDnATrX9VpkMcyFeM0FhwO62wh+A=
|
||||
github.com/benbjohnson/clock v0.0.0-20161215174838-7dc76406b6d3 h1:wOysYcIdqv3WnvwqFFzrYCFALPED7qkUGaLXu359GSc=
|
||||
github.com/benbjohnson/clock v0.0.0-20161215174838-7dc76406b6d3/go.mod h1:UMqtWQTnOe4byzwe7Zhwh8f8s+36uszN51sJrSIZlTE=
|
||||
github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973 h1:xJ4a3vCFaGF/jqvzLMYoU8P317H5OQ+Via4RmuPwCS0=
|
||||
@ -22,12 +24,16 @@ github.com/codahale/hdrhistogram v0.0.0-20161010025455-3a0bb77429bd h1:qMd81Ts1T
|
||||
github.com/codahale/hdrhistogram v0.0.0-20161010025455-3a0bb77429bd/go.mod h1:sE/e/2PUdi/liOCUjSTXgM1o87ZssimdTWN964YiIeI=
|
||||
github.com/codegangsta/cli v1.20.0 h1:iX1FXEgwzd5+XN6wk5cVHOGQj6Q3Dcp20lUeS4lHNTw=
|
||||
github.com/codegangsta/cli v1.20.0/go.mod h1:/qJNoX69yVSKu5o4jLyXAENLRyk1uhi7zkbQ3slBdOA=
|
||||
github.com/crewjam/saml v0.0.0-20190521120225-344d075952c9 h1:+cz/lCIhz+eg8+jC8cWk5LBLbbpH39IKyHliN6GZyUE=
|
||||
github.com/crewjam/saml v0.0.0-20190521120225-344d075952c9/go.mod h1:w5eu+HNtubx+kRpQL6QFT2F3yIFfYVe6+EzOFVU7Hko=
|
||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/denisenkom/go-mssqldb v0.0.0-20181014144952-4e0d7dc8888f/go.mod h1:xN/JuLBIz4bjkxNmByTiV1IbhfnYb6oo99phBn4Eqhc=
|
||||
github.com/denisenkom/go-mssqldb v0.0.0-20190315220205-a8ed825ac853 h1:tTngnoO/B6HQnJ+pK8tN7kEAhmhIfaJOutqq/A4/JTM=
|
||||
github.com/denisenkom/go-mssqldb v0.0.0-20190315220205-a8ed825ac853/go.mod h1:xN/JuLBIz4bjkxNmByTiV1IbhfnYb6oo99phBn4Eqhc=
|
||||
github.com/dgrijalva/jwt-go v3.2.0+incompatible h1:7qlOGliEKZXTDg6OTjfoBKDXWrumCAMpl/TFQ4/5kLM=
|
||||
github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ=
|
||||
github.com/facebookgo/ensure v0.0.0-20160127193407-b4ab57deab51 h1:0JZ+dUmQeA8IIVUMzysrX4/AKuQwWhV2dYQuPZdvdSQ=
|
||||
github.com/facebookgo/ensure v0.0.0-20160127193407-b4ab57deab51/go.mod h1:Yg+htXGokKKdzcwhuNDwVvN+uBxDGXJ7G/VN1d8fa64=
|
||||
github.com/facebookgo/inject v0.0.0-20180706035515-f23751cae28b h1:V6c4/dSTNhSaNn4c5ulbakfv277qCvs7byFYv7P83iQ=
|
||||
@ -105,6 +111,8 @@ github.com/jackc/pgx v3.2.0+incompatible h1:0Vihzu20St42/UDsvZGdNE6jak7oi/UOeMzw
|
||||
github.com/jackc/pgx v3.2.0+incompatible/go.mod h1:0ZGrqGqkRlliWnWB4zKnWtjbSWbGkVEFm4TeybAXq+I=
|
||||
github.com/jmespath/go-jmespath v0.0.0-20180206201540-c2b33e8439af h1:pmfjZENx5imkbgOkpRUYLnmbU7UEFbjtDA2hxJ1ichM=
|
||||
github.com/jmespath/go-jmespath v0.0.0-20180206201540-c2b33e8439af/go.mod h1:Nht3zPeWKUH0NzdCt2Blrr5ys8VGpn0CEB0cQHVjt7k=
|
||||
github.com/jonboulle/clockwork v0.1.0 h1:VKV+ZcuP6l3yW9doeqz6ziZGgcynBVQO+obU0+0hcPo=
|
||||
github.com/jonboulle/clockwork v0.1.0/go.mod h1:Ii8DK3G1RaLaWxj9trq07+26W01tbo22gdxWY5EU2bo=
|
||||
github.com/jtolds/gls v4.2.1+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU=
|
||||
github.com/jtolds/gls v4.20.0+incompatible h1:xdiiI2gbIgH/gLH7ADydsJ1uDOEzR8yvV7C0MuV77Wo=
|
||||
github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU=
|
||||
@ -169,6 +177,8 @@ github.com/rainycape/unidecode v0.0.0-20150907023854-cb7f23ec59be h1:ta7tUOvsPHV
|
||||
github.com/rainycape/unidecode v0.0.0-20150907023854-cb7f23ec59be/go.mod h1:MIDFMn7db1kT65GmV94GzpX9Qdi7N/pQlwb+AN8wh+Q=
|
||||
github.com/robfig/cron v0.0.0-20180505203441-b41be1df6967 h1:x7xEyJDP7Hv3LVgvWhzioQqbC/KtuUhTigKlH/8ehhE=
|
||||
github.com/robfig/cron v0.0.0-20180505203441-b41be1df6967/go.mod h1:JGuDeoQd7Z6yL4zQhZ3OPEVHB7fL6Ka6skscFHfmt2k=
|
||||
github.com/russellhaering/goxmldsig v0.0.0-20180430223755-7acd5e4a6ef7 h1:J4AOUcOh/t1XbQcJfkEqhzgvMJ2tDxdCVvmHxW5QXao=
|
||||
github.com/russellhaering/goxmldsig v0.0.0-20180430223755-7acd5e4a6ef7/go.mod h1:Oz4y6ImuOQZxynhbSXk7btjEfNBtGlj2dcaOvXl2FSM=
|
||||
github.com/satori/go.uuid v1.2.0 h1:0uYX9dsZ2yD7q2RtLRtPSdGDWzjeM3TbMJP9utgA0ww=
|
||||
github.com/satori/go.uuid v1.2.0/go.mod h1:dA0hQrYB0VpLJoorglMZABFdXlWrHn1NEOzdhQKdks0=
|
||||
github.com/sergi/go-diff v1.0.0 h1:Kpca3qRNrduNnOQeazBd0ysaKrUJiIuISHxogkT9RPQ=
|
||||
@ -185,6 +195,7 @@ github.com/smartystreets/goconvey v0.0.0-20181108003508-044398e4856c/go.mod h1:X
|
||||
github.com/smartystreets/goconvey v0.0.0-20190330032615-68dc04aab96a h1:pa8hGb/2YqsZKovtsgrwcDH1RZhVbTKCjLp47XpqCDs=
|
||||
github.com/smartystreets/goconvey v0.0.0-20190330032615-68dc04aab96a/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA=
|
||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||
github.com/stretchr/objx v0.1.1 h1:2vfRuCMp5sSVIDSqO8oNnWJq7mPa6KVP3iPIwFBuy8A=
|
||||
github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
|
||||
github.com/stretchr/testify v1.3.0 h1:TivCn/peBQ7UY8ooIcPgZFpTNSz0Q2U6UrFlUfqbe0Q=
|
||||
|
@ -1,8 +1,10 @@
|
||||
package extensions
|
||||
|
||||
import (
|
||||
_ "github.com/crewjam/saml"
|
||||
_ "github.com/gobwas/glob"
|
||||
_ "github.com/robfig/cron"
|
||||
_ "github.com/stretchr/testify/require"
|
||||
_ "gopkg.in/square/go-jose.v2"
|
||||
)
|
||||
|
||||
|
10
vendor/github.com/beevik/etree/CONTRIBUTORS
generated
vendored
Normal file
10
vendor/github.com/beevik/etree/CONTRIBUTORS
generated
vendored
Normal file
@ -0,0 +1,10 @@
|
||||
Brett Vickers (beevik)
|
||||
Felix Geisendörfer (felixge)
|
||||
Kamil Kisiel (kisielk)
|
||||
Graham King (grahamking)
|
||||
Matt Smith (ma314smith)
|
||||
Michal Jemala (michaljemala)
|
||||
Nicolas Piganeau (npiganeau)
|
||||
Chris Brown (ccbrown)
|
||||
Earncef Sequeira (earncef)
|
||||
Gabriel de Labachelerie (wuzuf)
|
24
vendor/github.com/beevik/etree/LICENSE
generated
vendored
Normal file
24
vendor/github.com/beevik/etree/LICENSE
generated
vendored
Normal file
@ -0,0 +1,24 @@
|
||||
Copyright 2015-2019 Brett Vickers. All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without
|
||||
modification, are permitted provided that the following conditions
|
||||
are met:
|
||||
|
||||
1. Redistributions of source code must retain the above copyright
|
||||
notice, this list of conditions and the following disclaimer.
|
||||
|
||||
2. Redistributions in binary form must reproduce the above copyright
|
||||
notice, this list of conditions and the following disclaimer in the
|
||||
documentation and/or other materials provided with the distribution.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY COPYRIGHT HOLDER ``AS IS'' AND ANY
|
||||
EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
|
||||
PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL COPYRIGHT HOLDER OR
|
||||
CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
|
||||
EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
|
||||
PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
|
||||
PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
|
||||
OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
205
vendor/github.com/beevik/etree/README.md
generated
vendored
Normal file
205
vendor/github.com/beevik/etree/README.md
generated
vendored
Normal file
@ -0,0 +1,205 @@
|
||||
[](https://travis-ci.org/beevik/etree)
|
||||
[](https://godoc.org/github.com/beevik/etree)
|
||||
|
||||
etree
|
||||
=====
|
||||
|
||||
The etree package is a lightweight, pure go package that expresses XML in
|
||||
the form of an element tree. Its design was inspired by the Python
|
||||
[ElementTree](http://docs.python.org/2/library/xml.etree.elementtree.html)
|
||||
module.
|
||||
|
||||
Some of the package's capabilities and features:
|
||||
|
||||
* Represents XML documents as trees of elements for easy traversal.
|
||||
* Imports, serializes, modifies or creates XML documents from scratch.
|
||||
* Writes and reads XML to/from files, byte slices, strings and io interfaces.
|
||||
* Performs simple or complex searches with lightweight XPath-like query APIs.
|
||||
* Auto-indents XML using spaces or tabs for better readability.
|
||||
* Implemented in pure go; depends only on standard go libraries.
|
||||
* Built on top of the go [encoding/xml](http://golang.org/pkg/encoding/xml)
|
||||
package.
|
||||
|
||||
### Creating an XML document
|
||||
|
||||
The following example creates an XML document from scratch using the etree
|
||||
package and outputs its indented contents to stdout.
|
||||
```go
|
||||
doc := etree.NewDocument()
|
||||
doc.CreateProcInst("xml", `version="1.0" encoding="UTF-8"`)
|
||||
doc.CreateProcInst("xml-stylesheet", `type="text/xsl" href="style.xsl"`)
|
||||
|
||||
people := doc.CreateElement("People")
|
||||
people.CreateComment("These are all known people")
|
||||
|
||||
jon := people.CreateElement("Person")
|
||||
jon.CreateAttr("name", "Jon")
|
||||
|
||||
sally := people.CreateElement("Person")
|
||||
sally.CreateAttr("name", "Sally")
|
||||
|
||||
doc.Indent(2)
|
||||
doc.WriteTo(os.Stdout)
|
||||
```
|
||||
|
||||
Output:
|
||||
```xml
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<?xml-stylesheet type="text/xsl" href="style.xsl"?>
|
||||
<People>
|
||||
<!--These are all known people-->
|
||||
<Person name="Jon"/>
|
||||
<Person name="Sally"/>
|
||||
</People>
|
||||
```
|
||||
|
||||
### Reading an XML file
|
||||
|
||||
Suppose you have a file on disk called `bookstore.xml` containing the
|
||||
following data:
|
||||
|
||||
```xml
|
||||
<bookstore xmlns:p="urn:schemas-books-com:prices">
|
||||
|
||||
<book category="COOKING">
|
||||
<title lang="en">Everyday Italian</title>
|
||||
<author>Giada De Laurentiis</author>
|
||||
<year>2005</year>
|
||||
<p:price>30.00</p:price>
|
||||
</book>
|
||||
|
||||
<book category="CHILDREN">
|
||||
<title lang="en">Harry Potter</title>
|
||||
<author>J K. Rowling</author>
|
||||
<year>2005</year>
|
||||
<p:price>29.99</p:price>
|
||||
</book>
|
||||
|
||||
<book category="WEB">
|
||||
<title lang="en">XQuery Kick Start</title>
|
||||
<author>James McGovern</author>
|
||||
<author>Per Bothner</author>
|
||||
<author>Kurt Cagle</author>
|
||||
<author>James Linn</author>
|
||||
<author>Vaidyanathan Nagarajan</author>
|
||||
<year>2003</year>
|
||||
<p:price>49.99</p:price>
|
||||
</book>
|
||||
|
||||
<book category="WEB">
|
||||
<title lang="en">Learning XML</title>
|
||||
<author>Erik T. Ray</author>
|
||||
<year>2003</year>
|
||||
<p:price>39.95</p:price>
|
||||
</book>
|
||||
|
||||
</bookstore>
|
||||
```
|
||||
|
||||
This code reads the file's contents into an etree document.
|
||||
```go
|
||||
doc := etree.NewDocument()
|
||||
if err := doc.ReadFromFile("bookstore.xml"); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
```
|
||||
|
||||
You can also read XML from a string, a byte slice, or an `io.Reader`.
|
||||
|
||||
### Processing elements and attributes
|
||||
|
||||
This example illustrates several ways to access elements and attributes using
|
||||
etree selection queries.
|
||||
```go
|
||||
root := doc.SelectElement("bookstore")
|
||||
fmt.Println("ROOT element:", root.Tag)
|
||||
|
||||
for _, book := range root.SelectElements("book") {
|
||||
fmt.Println("CHILD element:", book.Tag)
|
||||
if title := book.SelectElement("title"); title != nil {
|
||||
lang := title.SelectAttrValue("lang", "unknown")
|
||||
fmt.Printf(" TITLE: %s (%s)\n", title.Text(), lang)
|
||||
}
|
||||
for _, attr := range book.Attr {
|
||||
fmt.Printf(" ATTR: %s=%s\n", attr.Key, attr.Value)
|
||||
}
|
||||
}
|
||||
```
|
||||
Output:
|
||||
```
|
||||
ROOT element: bookstore
|
||||
CHILD element: book
|
||||
TITLE: Everyday Italian (en)
|
||||
ATTR: category=COOKING
|
||||
CHILD element: book
|
||||
TITLE: Harry Potter (en)
|
||||
ATTR: category=CHILDREN
|
||||
CHILD element: book
|
||||
TITLE: XQuery Kick Start (en)
|
||||
ATTR: category=WEB
|
||||
CHILD element: book
|
||||
TITLE: Learning XML (en)
|
||||
ATTR: category=WEB
|
||||
```
|
||||
|
||||
### Path queries
|
||||
|
||||
This example uses etree's path functions to select all book titles that fall
|
||||
into the category of 'WEB'. The double-slash prefix in the path causes the
|
||||
search for book elements to occur recursively; book elements may appear at any
|
||||
level of the XML hierarchy.
|
||||
```go
|
||||
for _, t := range doc.FindElements("//book[@category='WEB']/title") {
|
||||
fmt.Println("Title:", t.Text())
|
||||
}
|
||||
```
|
||||
|
||||
Output:
|
||||
```
|
||||
Title: XQuery Kick Start
|
||||
Title: Learning XML
|
||||
```
|
||||
|
||||
This example finds the first book element under the root bookstore element and
|
||||
outputs the tag and text of each of its child elements.
|
||||
```go
|
||||
for _, e := range doc.FindElements("./bookstore/book[1]/*") {
|
||||
fmt.Printf("%s: %s\n", e.Tag, e.Text())
|
||||
}
|
||||
```
|
||||
|
||||
Output:
|
||||
```
|
||||
title: Everyday Italian
|
||||
author: Giada De Laurentiis
|
||||
year: 2005
|
||||
price: 30.00
|
||||
```
|
||||
|
||||
This example finds all books with a price of 49.99 and outputs their titles.
|
||||
```go
|
||||
path := etree.MustCompilePath("./bookstore/book[p:price='49.99']/title")
|
||||
for _, e := range doc.FindElementsPath(path) {
|
||||
fmt.Println(e.Text())
|
||||
}
|
||||
```
|
||||
|
||||
Output:
|
||||
```
|
||||
XQuery Kick Start
|
||||
```
|
||||
|
||||
Note that this example uses the FindElementsPath function, which takes as an
|
||||
argument a pre-compiled path object. Use precompiled paths when you plan to
|
||||
search with the same path more than once.
|
||||
|
||||
### Other features
|
||||
|
||||
These are just a few examples of the things the etree package can do. See the
|
||||
[documentation](http://godoc.org/github.com/beevik/etree) for a complete
|
||||
description of its capabilities.
|
||||
|
||||
### Contributing
|
||||
|
||||
This project accepts contributions. Just fork the repo and submit a pull
|
||||
request!
|
109
vendor/github.com/beevik/etree/RELEASE_NOTES.md
generated
vendored
Normal file
109
vendor/github.com/beevik/etree/RELEASE_NOTES.md
generated
vendored
Normal file
@ -0,0 +1,109 @@
|
||||
Release v1.1.0
|
||||
==============
|
||||
|
||||
**New Features**
|
||||
|
||||
* New attribute helpers.
|
||||
* Added the `Element.SortAttrs` method, which lexicographically sorts an
|
||||
element's attributes by key.
|
||||
* New `ReadSettings` properties.
|
||||
* Added `Entity` for the support of custom entity maps.
|
||||
* New `WriteSettings` properties.
|
||||
* Added `UseCRLF` to allow the output of CR-LF newlines instead of the
|
||||
default LF newlines. This is useful on Windows systems.
|
||||
* Additional support for text and CDATA sections.
|
||||
* The `Element.Text` method now returns the concatenation of all consecutive
|
||||
character data tokens immediately following an element's opening tag.
|
||||
* Added `Element.SetCData` to replace the character data immediately
|
||||
following an element's opening tag with a CDATA section.
|
||||
* Added `Element.CreateCData` to create and add a CDATA section child
|
||||
`CharData` token to an element.
|
||||
* Added `Element.CreateText` to create and add a child text `CharData` token
|
||||
to an element.
|
||||
* Added `NewCData` to create a parentless CDATA section `CharData` token.
|
||||
* Added `NewText` to create a parentless text `CharData`
|
||||
token.
|
||||
* Added `CharData.IsCData` to detect if the token contains a CDATA section.
|
||||
* Added `CharData.IsWhitespace` to detect if the token contains whitespace
|
||||
inserted by one of the document Indent functions.
|
||||
* Modified `Element.SetText` so that it replaces a run of consecutive
|
||||
character data tokens following the element's opening tag (instead of just
|
||||
the first one).
|
||||
* New "tail text" support.
|
||||
* Added the `Element.Tail` method, which returns the text immediately
|
||||
following an element's closing tag.
|
||||
* Added the `Element.SetTail` method, which modifies the text immediately
|
||||
following an element's closing tag.
|
||||
* New element child insertion and removal methods.
|
||||
* Added the `Element.InsertChildAt` method, which inserts a new child token
|
||||
before the specified child token index.
|
||||
* Added the `Element.RemoveChildAt` method, which removes the child token at
|
||||
the specified child token index.
|
||||
* New element and attribute queries.
|
||||
* Added the `Element.Index` method, which returns the element's index within
|
||||
its parent element's child token list.
|
||||
* Added the `Element.NamespaceURI` method to return the namespace URI
|
||||
associated with an element.
|
||||
* Added the `Attr.NamespaceURI` method to return the namespace URI
|
||||
associated with an element.
|
||||
* Added the `Attr.Element` method to return the element that an attribute
|
||||
belongs to.
|
||||
* New Path filter functions.
|
||||
* Added `[local-name()='val']` to keep elements whose unprefixed tag matches
|
||||
the desired value.
|
||||
* Added `[name()='val']` to keep elements whose full tag matches the desired
|
||||
value.
|
||||
* Added `[namespace-prefix()='val']` to keep elements whose namespace prefix
|
||||
matches the desired value.
|
||||
* Added `[namespace-uri()='val']` to keep elements whose namespace URI
|
||||
matches the desired value.
|
||||
|
||||
**Bug Fixes**
|
||||
|
||||
* A default XML `CharSetReader` is now used to prevent failed parsing of XML
|
||||
documents using certain encodings.
|
||||
([Issue](https://github.com/beevik/etree/issues/53)).
|
||||
* All characters are now properly escaped according to XML parsing rules.
|
||||
([Issue](https://github.com/beevik/etree/issues/55)).
|
||||
* The `Document.Indent` and `Document.IndentTabs` functions no longer insert
|
||||
empty string `CharData` tokens.
|
||||
|
||||
**Deprecated**
|
||||
|
||||
* `Element`
|
||||
* The `InsertChild` method is deprecated. Use `InsertChildAt` instead.
|
||||
* The `CreateCharData` method is deprecated. Use `CreateText` instead.
|
||||
* `CharData`
|
||||
* The `NewCharData` method is deprecated. Use `NewText` instead.
|
||||
|
||||
|
||||
Release v1.0.1
|
||||
==============
|
||||
|
||||
**Changes**
|
||||
|
||||
* Added support for absolute etree Path queries. An absolute path begins with
|
||||
`/` or `//` and begins its search from the element's document root.
|
||||
* Added [`GetPath`](https://godoc.org/github.com/beevik/etree#Element.GetPath)
|
||||
and [`GetRelativePath`](https://godoc.org/github.com/beevik/etree#Element.GetRelativePath)
|
||||
functions to the [`Element`](https://godoc.org/github.com/beevik/etree#Element)
|
||||
type.
|
||||
|
||||
**Breaking changes**
|
||||
|
||||
* A path starting with `//` is now interpreted as an absolute path.
|
||||
Previously, it was interpreted as a relative path starting from the element
|
||||
whose
|
||||
[`FindElement`](https://godoc.org/github.com/beevik/etree#Element.FindElement)
|
||||
method was called. To remain compatible with this release, all paths
|
||||
prefixed with `//` should be prefixed with `.//` when called from any
|
||||
element other than the document's root.
|
||||
* [**edit 2/1/2019**]: Minor releases should not contain breaking changes.
|
||||
Even though this breaking change was very minor, it was a mistake to include
|
||||
it in this minor release. In the future, all breaking changes will be
|
||||
limited to major releases (e.g., version 2.0.0).
|
||||
|
||||
Release v1.0.0
|
||||
==============
|
||||
|
||||
Initial release.
|
1453
vendor/github.com/beevik/etree/etree.go
generated
vendored
Normal file
1453
vendor/github.com/beevik/etree/etree.go
generated
vendored
Normal file
File diff suppressed because it is too large
Load Diff
276
vendor/github.com/beevik/etree/helpers.go
generated
vendored
Normal file
276
vendor/github.com/beevik/etree/helpers.go
generated
vendored
Normal file
@ -0,0 +1,276 @@
|
||||
// Copyright 2015-2019 Brett Vickers.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package etree
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"io"
|
||||
"strings"
|
||||
"unicode/utf8"
|
||||
)
|
||||
|
||||
// A simple stack
|
||||
type stack struct {
|
||||
data []interface{}
|
||||
}
|
||||
|
||||
func (s *stack) empty() bool {
|
||||
return len(s.data) == 0
|
||||
}
|
||||
|
||||
func (s *stack) push(value interface{}) {
|
||||
s.data = append(s.data, value)
|
||||
}
|
||||
|
||||
func (s *stack) pop() interface{} {
|
||||
value := s.data[len(s.data)-1]
|
||||
s.data[len(s.data)-1] = nil
|
||||
s.data = s.data[:len(s.data)-1]
|
||||
return value
|
||||
}
|
||||
|
||||
func (s *stack) peek() interface{} {
|
||||
return s.data[len(s.data)-1]
|
||||
}
|
||||
|
||||
// A fifo is a simple first-in-first-out queue.
|
||||
type fifo struct {
|
||||
data []interface{}
|
||||
head, tail int
|
||||
}
|
||||
|
||||
func (f *fifo) add(value interface{}) {
|
||||
if f.len()+1 >= len(f.data) {
|
||||
f.grow()
|
||||
}
|
||||
f.data[f.tail] = value
|
||||
if f.tail++; f.tail == len(f.data) {
|
||||
f.tail = 0
|
||||
}
|
||||
}
|
||||
|
||||
func (f *fifo) remove() interface{} {
|
||||
value := f.data[f.head]
|
||||
f.data[f.head] = nil
|
||||
if f.head++; f.head == len(f.data) {
|
||||
f.head = 0
|
||||
}
|
||||
return value
|
||||
}
|
||||
|
||||
func (f *fifo) len() int {
|
||||
if f.tail >= f.head {
|
||||
return f.tail - f.head
|
||||
}
|
||||
return len(f.data) - f.head + f.tail
|
||||
}
|
||||
|
||||
func (f *fifo) grow() {
|
||||
c := len(f.data) * 2
|
||||
if c == 0 {
|
||||
c = 4
|
||||
}
|
||||
buf, count := make([]interface{}, c), f.len()
|
||||
if f.tail >= f.head {
|
||||
copy(buf[0:count], f.data[f.head:f.tail])
|
||||
} else {
|
||||
hindex := len(f.data) - f.head
|
||||
copy(buf[0:hindex], f.data[f.head:])
|
||||
copy(buf[hindex:count], f.data[:f.tail])
|
||||
}
|
||||
f.data, f.head, f.tail = buf, 0, count
|
||||
}
|
||||
|
||||
// countReader implements a proxy reader that counts the number of
|
||||
// bytes read from its encapsulated reader.
|
||||
type countReader struct {
|
||||
r io.Reader
|
||||
bytes int64
|
||||
}
|
||||
|
||||
func newCountReader(r io.Reader) *countReader {
|
||||
return &countReader{r: r}
|
||||
}
|
||||
|
||||
func (cr *countReader) Read(p []byte) (n int, err error) {
|
||||
b, err := cr.r.Read(p)
|
||||
cr.bytes += int64(b)
|
||||
return b, err
|
||||
}
|
||||
|
||||
// countWriter implements a proxy writer that counts the number of
|
||||
// bytes written by its encapsulated writer.
|
||||
type countWriter struct {
|
||||
w io.Writer
|
||||
bytes int64
|
||||
}
|
||||
|
||||
func newCountWriter(w io.Writer) *countWriter {
|
||||
return &countWriter{w: w}
|
||||
}
|
||||
|
||||
func (cw *countWriter) Write(p []byte) (n int, err error) {
|
||||
b, err := cw.w.Write(p)
|
||||
cw.bytes += int64(b)
|
||||
return b, err
|
||||
}
|
||||
|
||||
// isWhitespace returns true if the byte slice contains only
|
||||
// whitespace characters.
|
||||
func isWhitespace(s string) bool {
|
||||
for i := 0; i < len(s); i++ {
|
||||
if c := s[i]; c != ' ' && c != '\t' && c != '\n' && c != '\r' {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
// spaceMatch returns true if namespace a is the empty string
|
||||
// or if namespace a equals namespace b.
|
||||
func spaceMatch(a, b string) bool {
|
||||
switch {
|
||||
case a == "":
|
||||
return true
|
||||
default:
|
||||
return a == b
|
||||
}
|
||||
}
|
||||
|
||||
// spaceDecompose breaks a namespace:tag identifier at the ':'
|
||||
// and returns the two parts.
|
||||
func spaceDecompose(str string) (space, key string) {
|
||||
colon := strings.IndexByte(str, ':')
|
||||
if colon == -1 {
|
||||
return "", str
|
||||
}
|
||||
return str[:colon], str[colon+1:]
|
||||
}
|
||||
|
||||
// Strings used by indentCRLF and indentLF
|
||||
const (
|
||||
indentSpaces = "\r\n "
|
||||
indentTabs = "\r\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t"
|
||||
)
|
||||
|
||||
// indentCRLF returns a CRLF newline followed by n copies of the first
|
||||
// non-CRLF character in the source string.
|
||||
func indentCRLF(n int, source string) string {
|
||||
switch {
|
||||
case n < 0:
|
||||
return source[:2]
|
||||
case n < len(source)-1:
|
||||
return source[:n+2]
|
||||
default:
|
||||
return source + strings.Repeat(source[2:3], n-len(source)+2)
|
||||
}
|
||||
}
|
||||
|
||||
// indentLF returns a LF newline followed by n copies of the first non-LF
|
||||
// character in the source string.
|
||||
func indentLF(n int, source string) string {
|
||||
switch {
|
||||
case n < 0:
|
||||
return source[1:2]
|
||||
case n < len(source)-1:
|
||||
return source[1 : n+2]
|
||||
default:
|
||||
return source[1:] + strings.Repeat(source[2:3], n-len(source)+2)
|
||||
}
|
||||
}
|
||||
|
||||
// nextIndex returns the index of the next occurrence of sep in s,
|
||||
// starting from offset. It returns -1 if the sep string is not found.
|
||||
func nextIndex(s, sep string, offset int) int {
|
||||
switch i := strings.Index(s[offset:], sep); i {
|
||||
case -1:
|
||||
return -1
|
||||
default:
|
||||
return offset + i
|
||||
}
|
||||
}
|
||||
|
||||
// isInteger returns true if the string s contains an integer.
|
||||
func isInteger(s string) bool {
|
||||
for i := 0; i < len(s); i++ {
|
||||
if (s[i] < '0' || s[i] > '9') && !(i == 0 && s[i] == '-') {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
type escapeMode byte
|
||||
|
||||
const (
|
||||
escapeNormal escapeMode = iota
|
||||
escapeCanonicalText
|
||||
escapeCanonicalAttr
|
||||
)
|
||||
|
||||
// escapeString writes an escaped version of a string to the writer.
|
||||
func escapeString(w *bufio.Writer, s string, m escapeMode) {
|
||||
var esc []byte
|
||||
last := 0
|
||||
for i := 0; i < len(s); {
|
||||
r, width := utf8.DecodeRuneInString(s[i:])
|
||||
i += width
|
||||
switch r {
|
||||
case '&':
|
||||
esc = []byte("&")
|
||||
case '<':
|
||||
esc = []byte("<")
|
||||
case '>':
|
||||
if m == escapeCanonicalAttr {
|
||||
continue
|
||||
}
|
||||
esc = []byte(">")
|
||||
case '\'':
|
||||
if m != escapeNormal {
|
||||
continue
|
||||
}
|
||||
esc = []byte("'")
|
||||
case '"':
|
||||
if m == escapeCanonicalText {
|
||||
continue
|
||||
}
|
||||
esc = []byte(""")
|
||||
case '\t':
|
||||
if m != escapeCanonicalAttr {
|
||||
continue
|
||||
}
|
||||
esc = []byte("	")
|
||||
case '\n':
|
||||
if m != escapeCanonicalAttr {
|
||||
continue
|
||||
}
|
||||
esc = []byte("
")
|
||||
case '\r':
|
||||
if m == escapeNormal {
|
||||
continue
|
||||
}
|
||||
esc = []byte("
")
|
||||
default:
|
||||
if !isInCharacterRange(r) || (r == 0xFFFD && width == 1) {
|
||||
esc = []byte("\uFFFD")
|
||||
break
|
||||
}
|
||||
continue
|
||||
}
|
||||
w.WriteString(s[last : i-width])
|
||||
w.Write(esc)
|
||||
last = i
|
||||
}
|
||||
w.WriteString(s[last:])
|
||||
}
|
||||
|
||||
func isInCharacterRange(r rune) bool {
|
||||
return r == 0x09 ||
|
||||
r == 0x0A ||
|
||||
r == 0x0D ||
|
||||
r >= 0x20 && r <= 0xD7FF ||
|
||||
r >= 0xE000 && r <= 0xFFFD ||
|
||||
r >= 0x10000 && r <= 0x10FFFF
|
||||
}
|
582
vendor/github.com/beevik/etree/path.go
generated
vendored
Normal file
582
vendor/github.com/beevik/etree/path.go
generated
vendored
Normal file
@ -0,0 +1,582 @@
|
||||
// Copyright 2015-2019 Brett Vickers.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package etree
|
||||
|
||||
import (
|
||||
"strconv"
|
||||
"strings"
|
||||
)
|
||||
|
||||
/*
|
||||
A Path is a string that represents a search path through an etree starting
|
||||
from the document root or an arbitrary element. Paths are used with the
|
||||
Element object's Find* methods to locate and return desired elements.
|
||||
|
||||
A Path consists of a series of slash-separated "selectors", each of which may
|
||||
be modified by one or more bracket-enclosed "filters". Selectors are used to
|
||||
traverse the etree from element to element, while filters are used to narrow
|
||||
the list of candidate elements at each node.
|
||||
|
||||
Although etree Path strings are similar to XPath strings
|
||||
(https://www.w3.org/TR/1999/REC-xpath-19991116/), they have a more limited set
|
||||
of selectors and filtering options.
|
||||
|
||||
The following selectors are supported by etree Path strings:
|
||||
|
||||
. Select the current element.
|
||||
.. Select the parent of the current element.
|
||||
* Select all child elements of the current element.
|
||||
/ Select the root element when used at the start of a path.
|
||||
// Select all descendants of the current element.
|
||||
tag Select all child elements with a name matching the tag.
|
||||
|
||||
The following basic filters are supported by etree Path strings:
|
||||
|
||||
[@attrib] Keep elements with an attribute named attrib.
|
||||
[@attrib='val'] Keep elements with an attribute named attrib and value matching val.
|
||||
[tag] Keep elements with a child element named tag.
|
||||
[tag='val'] Keep elements with a child element named tag and text matching val.
|
||||
[n] Keep the n-th element, where n is a numeric index starting from 1.
|
||||
|
||||
The following function filters are also supported:
|
||||
|
||||
[text()] Keep elements with non-empty text.
|
||||
[text()='val'] Keep elements whose text matches val.
|
||||
[local-name()='val'] Keep elements whose un-prefixed tag matches val.
|
||||
[name()='val'] Keep elements whose full tag exactly matches val.
|
||||
[namespace-prefix()='val'] Keep elements whose namespace prefix matches val.
|
||||
[namespace-uri()='val'] Keep elements whose namespace URI matches val.
|
||||
|
||||
Here are some examples of Path strings:
|
||||
|
||||
- Select the bookstore child element of the root element:
|
||||
/bookstore
|
||||
|
||||
- Beginning from the root element, select the title elements of all
|
||||
descendant book elements having a 'category' attribute of 'WEB':
|
||||
//book[@category='WEB']/title
|
||||
|
||||
- Beginning from the current element, select the first descendant
|
||||
book element with a title child element containing the text 'Great
|
||||
Expectations':
|
||||
.//book[title='Great Expectations'][1]
|
||||
|
||||
- Beginning from the current element, select all child elements of
|
||||
book elements with an attribute 'language' set to 'english':
|
||||
./book/*[@language='english']
|
||||
|
||||
- Beginning from the current element, select all child elements of
|
||||
book elements containing the text 'special':
|
||||
./book/*[text()='special']
|
||||
|
||||
- Beginning from the current element, select all descendant book
|
||||
elements whose title child element has a 'language' attribute of 'french':
|
||||
.//book/title[@language='french']/..
|
||||
|
||||
- Beginning from the current element, select all book elements
|
||||
belonging to the http://www.w3.org/TR/html4/ namespace:
|
||||
.//book[namespace-uri()='http://www.w3.org/TR/html4/']
|
||||
|
||||
*/
|
||||
type Path struct {
|
||||
segments []segment
|
||||
}
|
||||
|
||||
// ErrPath is returned by path functions when an invalid etree path is provided.
|
||||
type ErrPath string
|
||||
|
||||
// Error returns the string describing a path error.
|
||||
func (err ErrPath) Error() string {
|
||||
return "etree: " + string(err)
|
||||
}
|
||||
|
||||
// CompilePath creates an optimized version of an XPath-like string that
|
||||
// can be used to query elements in an element tree.
|
||||
func CompilePath(path string) (Path, error) {
|
||||
var comp compiler
|
||||
segments := comp.parsePath(path)
|
||||
if comp.err != ErrPath("") {
|
||||
return Path{nil}, comp.err
|
||||
}
|
||||
return Path{segments}, nil
|
||||
}
|
||||
|
||||
// MustCompilePath creates an optimized version of an XPath-like string that
|
||||
// can be used to query elements in an element tree. Panics if an error
|
||||
// occurs. Use this function to create Paths when you know the path is
|
||||
// valid (i.e., if it's hard-coded).
|
||||
func MustCompilePath(path string) Path {
|
||||
p, err := CompilePath(path)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return p
|
||||
}
|
||||
|
||||
// A segment is a portion of a path between "/" characters.
|
||||
// It contains one selector and zero or more [filters].
|
||||
type segment struct {
|
||||
sel selector
|
||||
filters []filter
|
||||
}
|
||||
|
||||
func (seg *segment) apply(e *Element, p *pather) {
|
||||
seg.sel.apply(e, p)
|
||||
for _, f := range seg.filters {
|
||||
f.apply(p)
|
||||
}
|
||||
}
|
||||
|
||||
// A selector selects XML elements for consideration by the
|
||||
// path traversal.
|
||||
type selector interface {
|
||||
apply(e *Element, p *pather)
|
||||
}
|
||||
|
||||
// A filter pares down a list of candidate XML elements based
|
||||
// on a path filter in [brackets].
|
||||
type filter interface {
|
||||
apply(p *pather)
|
||||
}
|
||||
|
||||
// A pather is helper object that traverses an element tree using
|
||||
// a Path object. It collects and deduplicates all elements matching
|
||||
// the path query.
|
||||
type pather struct {
|
||||
queue fifo
|
||||
results []*Element
|
||||
inResults map[*Element]bool
|
||||
candidates []*Element
|
||||
scratch []*Element // used by filters
|
||||
}
|
||||
|
||||
// A node represents an element and the remaining path segments that
|
||||
// should be applied against it by the pather.
|
||||
type node struct {
|
||||
e *Element
|
||||
segments []segment
|
||||
}
|
||||
|
||||
func newPather() *pather {
|
||||
return &pather{
|
||||
results: make([]*Element, 0),
|
||||
inResults: make(map[*Element]bool),
|
||||
candidates: make([]*Element, 0),
|
||||
scratch: make([]*Element, 0),
|
||||
}
|
||||
}
|
||||
|
||||
// traverse follows the path from the element e, collecting
|
||||
// and then returning all elements that match the path's selectors
|
||||
// and filters.
|
||||
func (p *pather) traverse(e *Element, path Path) []*Element {
|
||||
for p.queue.add(node{e, path.segments}); p.queue.len() > 0; {
|
||||
p.eval(p.queue.remove().(node))
|
||||
}
|
||||
return p.results
|
||||
}
|
||||
|
||||
// eval evalutes the current path node by applying the remaining
|
||||
// path's selector rules against the node's element.
|
||||
func (p *pather) eval(n node) {
|
||||
p.candidates = p.candidates[0:0]
|
||||
seg, remain := n.segments[0], n.segments[1:]
|
||||
seg.apply(n.e, p)
|
||||
|
||||
if len(remain) == 0 {
|
||||
for _, c := range p.candidates {
|
||||
if in := p.inResults[c]; !in {
|
||||
p.inResults[c] = true
|
||||
p.results = append(p.results, c)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
for _, c := range p.candidates {
|
||||
p.queue.add(node{c, remain})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// A compiler generates a compiled path from a path string.
|
||||
type compiler struct {
|
||||
err ErrPath
|
||||
}
|
||||
|
||||
// parsePath parses an XPath-like string describing a path
|
||||
// through an element tree and returns a slice of segment
|
||||
// descriptors.
|
||||
func (c *compiler) parsePath(path string) []segment {
|
||||
// If path ends with //, fix it
|
||||
if strings.HasSuffix(path, "//") {
|
||||
path = path + "*"
|
||||
}
|
||||
|
||||
var segments []segment
|
||||
|
||||
// Check for an absolute path
|
||||
if strings.HasPrefix(path, "/") {
|
||||
segments = append(segments, segment{new(selectRoot), []filter{}})
|
||||
path = path[1:]
|
||||
}
|
||||
|
||||
// Split path into segments
|
||||
for _, s := range splitPath(path) {
|
||||
segments = append(segments, c.parseSegment(s))
|
||||
if c.err != ErrPath("") {
|
||||
break
|
||||
}
|
||||
}
|
||||
return segments
|
||||
}
|
||||
|
||||
func splitPath(path string) []string {
|
||||
pieces := make([]string, 0)
|
||||
start := 0
|
||||
inquote := false
|
||||
for i := 0; i+1 <= len(path); i++ {
|
||||
if path[i] == '\'' {
|
||||
inquote = !inquote
|
||||
} else if path[i] == '/' && !inquote {
|
||||
pieces = append(pieces, path[start:i])
|
||||
start = i + 1
|
||||
}
|
||||
}
|
||||
return append(pieces, path[start:])
|
||||
}
|
||||
|
||||
// parseSegment parses a path segment between / characters.
|
||||
func (c *compiler) parseSegment(path string) segment {
|
||||
pieces := strings.Split(path, "[")
|
||||
seg := segment{
|
||||
sel: c.parseSelector(pieces[0]),
|
||||
filters: []filter{},
|
||||
}
|
||||
for i := 1; i < len(pieces); i++ {
|
||||
fpath := pieces[i]
|
||||
if fpath[len(fpath)-1] != ']' {
|
||||
c.err = ErrPath("path has invalid filter [brackets].")
|
||||
break
|
||||
}
|
||||
seg.filters = append(seg.filters, c.parseFilter(fpath[:len(fpath)-1]))
|
||||
}
|
||||
return seg
|
||||
}
|
||||
|
||||
// parseSelector parses a selector at the start of a path segment.
|
||||
func (c *compiler) parseSelector(path string) selector {
|
||||
switch path {
|
||||
case ".":
|
||||
return new(selectSelf)
|
||||
case "..":
|
||||
return new(selectParent)
|
||||
case "*":
|
||||
return new(selectChildren)
|
||||
case "":
|
||||
return new(selectDescendants)
|
||||
default:
|
||||
return newSelectChildrenByTag(path)
|
||||
}
|
||||
}
|
||||
|
||||
var fnTable = map[string]struct {
|
||||
hasFn func(e *Element) bool
|
||||
getValFn func(e *Element) string
|
||||
}{
|
||||
"local-name": {nil, (*Element).name},
|
||||
"name": {nil, (*Element).FullTag},
|
||||
"namespace-prefix": {nil, (*Element).namespacePrefix},
|
||||
"namespace-uri": {nil, (*Element).NamespaceURI},
|
||||
"text": {(*Element).hasText, (*Element).Text},
|
||||
}
|
||||
|
||||
// parseFilter parses a path filter contained within [brackets].
|
||||
func (c *compiler) parseFilter(path string) filter {
|
||||
if len(path) == 0 {
|
||||
c.err = ErrPath("path contains an empty filter expression.")
|
||||
return nil
|
||||
}
|
||||
|
||||
// Filter contains [@attr='val'], [fn()='val'], or [tag='val']?
|
||||
eqindex := strings.Index(path, "='")
|
||||
if eqindex >= 0 {
|
||||
rindex := nextIndex(path, "'", eqindex+2)
|
||||
if rindex != len(path)-1 {
|
||||
c.err = ErrPath("path has mismatched filter quotes.")
|
||||
return nil
|
||||
}
|
||||
|
||||
key := path[:eqindex]
|
||||
value := path[eqindex+2 : rindex]
|
||||
|
||||
switch {
|
||||
case key[0] == '@':
|
||||
return newFilterAttrVal(key[1:], value)
|
||||
case strings.HasSuffix(key, "()"):
|
||||
fn := key[:len(key)-2]
|
||||
if t, ok := fnTable[fn]; ok && t.getValFn != nil {
|
||||
return newFilterFuncVal(t.getValFn, value)
|
||||
}
|
||||
c.err = ErrPath("path has unknown function " + fn)
|
||||
return nil
|
||||
default:
|
||||
return newFilterChildText(key, value)
|
||||
}
|
||||
}
|
||||
|
||||
// Filter contains [@attr], [N], [tag] or [fn()]
|
||||
switch {
|
||||
case path[0] == '@':
|
||||
return newFilterAttr(path[1:])
|
||||
case strings.HasSuffix(path, "()"):
|
||||
fn := path[:len(path)-2]
|
||||
if t, ok := fnTable[fn]; ok && t.hasFn != nil {
|
||||
return newFilterFunc(t.hasFn)
|
||||
}
|
||||
c.err = ErrPath("path has unknown function " + fn)
|
||||
return nil
|
||||
case isInteger(path):
|
||||
pos, _ := strconv.Atoi(path)
|
||||
switch {
|
||||
case pos > 0:
|
||||
return newFilterPos(pos - 1)
|
||||
default:
|
||||
return newFilterPos(pos)
|
||||
}
|
||||
default:
|
||||
return newFilterChild(path)
|
||||
}
|
||||
}
|
||||
|
||||
// selectSelf selects the current element into the candidate list.
|
||||
type selectSelf struct{}
|
||||
|
||||
func (s *selectSelf) apply(e *Element, p *pather) {
|
||||
p.candidates = append(p.candidates, e)
|
||||
}
|
||||
|
||||
// selectRoot selects the element's root node.
|
||||
type selectRoot struct{}
|
||||
|
||||
func (s *selectRoot) apply(e *Element, p *pather) {
|
||||
root := e
|
||||
for root.parent != nil {
|
||||
root = root.parent
|
||||
}
|
||||
p.candidates = append(p.candidates, root)
|
||||
}
|
||||
|
||||
// selectParent selects the element's parent into the candidate list.
|
||||
type selectParent struct{}
|
||||
|
||||
func (s *selectParent) apply(e *Element, p *pather) {
|
||||
if e.parent != nil {
|
||||
p.candidates = append(p.candidates, e.parent)
|
||||
}
|
||||
}
|
||||
|
||||
// selectChildren selects the element's child elements into the
|
||||
// candidate list.
|
||||
type selectChildren struct{}
|
||||
|
||||
func (s *selectChildren) apply(e *Element, p *pather) {
|
||||
for _, c := range e.Child {
|
||||
if c, ok := c.(*Element); ok {
|
||||
p.candidates = append(p.candidates, c)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// selectDescendants selects all descendant child elements
|
||||
// of the element into the candidate list.
|
||||
type selectDescendants struct{}
|
||||
|
||||
func (s *selectDescendants) apply(e *Element, p *pather) {
|
||||
var queue fifo
|
||||
for queue.add(e); queue.len() > 0; {
|
||||
e := queue.remove().(*Element)
|
||||
p.candidates = append(p.candidates, e)
|
||||
for _, c := range e.Child {
|
||||
if c, ok := c.(*Element); ok {
|
||||
queue.add(c)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// selectChildrenByTag selects into the candidate list all child
|
||||
// elements of the element having the specified tag.
|
||||
type selectChildrenByTag struct {
|
||||
space, tag string
|
||||
}
|
||||
|
||||
func newSelectChildrenByTag(path string) *selectChildrenByTag {
|
||||
s, l := spaceDecompose(path)
|
||||
return &selectChildrenByTag{s, l}
|
||||
}
|
||||
|
||||
func (s *selectChildrenByTag) apply(e *Element, p *pather) {
|
||||
for _, c := range e.Child {
|
||||
if c, ok := c.(*Element); ok && spaceMatch(s.space, c.Space) && s.tag == c.Tag {
|
||||
p.candidates = append(p.candidates, c)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// filterPos filters the candidate list, keeping only the
|
||||
// candidate at the specified index.
|
||||
type filterPos struct {
|
||||
index int
|
||||
}
|
||||
|
||||
func newFilterPos(pos int) *filterPos {
|
||||
return &filterPos{pos}
|
||||
}
|
||||
|
||||
func (f *filterPos) apply(p *pather) {
|
||||
if f.index >= 0 {
|
||||
if f.index < len(p.candidates) {
|
||||
p.scratch = append(p.scratch, p.candidates[f.index])
|
||||
}
|
||||
} else {
|
||||
if -f.index <= len(p.candidates) {
|
||||
p.scratch = append(p.scratch, p.candidates[len(p.candidates)+f.index])
|
||||
}
|
||||
}
|
||||
p.candidates, p.scratch = p.scratch, p.candidates[0:0]
|
||||
}
|
||||
|
||||
// filterAttr filters the candidate list for elements having
|
||||
// the specified attribute.
|
||||
type filterAttr struct {
|
||||
space, key string
|
||||
}
|
||||
|
||||
func newFilterAttr(str string) *filterAttr {
|
||||
s, l := spaceDecompose(str)
|
||||
return &filterAttr{s, l}
|
||||
}
|
||||
|
||||
func (f *filterAttr) apply(p *pather) {
|
||||
for _, c := range p.candidates {
|
||||
for _, a := range c.Attr {
|
||||
if spaceMatch(f.space, a.Space) && f.key == a.Key {
|
||||
p.scratch = append(p.scratch, c)
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
p.candidates, p.scratch = p.scratch, p.candidates[0:0]
|
||||
}
|
||||
|
||||
// filterAttrVal filters the candidate list for elements having
|
||||
// the specified attribute with the specified value.
|
||||
type filterAttrVal struct {
|
||||
space, key, val string
|
||||
}
|
||||
|
||||
func newFilterAttrVal(str, value string) *filterAttrVal {
|
||||
s, l := spaceDecompose(str)
|
||||
return &filterAttrVal{s, l, value}
|
||||
}
|
||||
|
||||
func (f *filterAttrVal) apply(p *pather) {
|
||||
for _, c := range p.candidates {
|
||||
for _, a := range c.Attr {
|
||||
if spaceMatch(f.space, a.Space) && f.key == a.Key && f.val == a.Value {
|
||||
p.scratch = append(p.scratch, c)
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
p.candidates, p.scratch = p.scratch, p.candidates[0:0]
|
||||
}
|
||||
|
||||
// filterFunc filters the candidate list for elements satisfying a custom
|
||||
// boolean function.
|
||||
type filterFunc struct {
|
||||
fn func(e *Element) bool
|
||||
}
|
||||
|
||||
func newFilterFunc(fn func(e *Element) bool) *filterFunc {
|
||||
return &filterFunc{fn}
|
||||
}
|
||||
|
||||
func (f *filterFunc) apply(p *pather) {
|
||||
for _, c := range p.candidates {
|
||||
if f.fn(c) {
|
||||
p.scratch = append(p.scratch, c)
|
||||
}
|
||||
}
|
||||
p.candidates, p.scratch = p.scratch, p.candidates[0:0]
|
||||
}
|
||||
|
||||
// filterFuncVal filters the candidate list for elements containing a value
|
||||
// matching the result of a custom function.
|
||||
type filterFuncVal struct {
|
||||
fn func(e *Element) string
|
||||
val string
|
||||
}
|
||||
|
||||
func newFilterFuncVal(fn func(e *Element) string, value string) *filterFuncVal {
|
||||
return &filterFuncVal{fn, value}
|
||||
}
|
||||
|
||||
func (f *filterFuncVal) apply(p *pather) {
|
||||
for _, c := range p.candidates {
|
||||
if f.fn(c) == f.val {
|
||||
p.scratch = append(p.scratch, c)
|
||||
}
|
||||
}
|
||||
p.candidates, p.scratch = p.scratch, p.candidates[0:0]
|
||||
}
|
||||
|
||||
// filterChild filters the candidate list for elements having
|
||||
// a child element with the specified tag.
|
||||
type filterChild struct {
|
||||
space, tag string
|
||||
}
|
||||
|
||||
func newFilterChild(str string) *filterChild {
|
||||
s, l := spaceDecompose(str)
|
||||
return &filterChild{s, l}
|
||||
}
|
||||
|
||||
func (f *filterChild) apply(p *pather) {
|
||||
for _, c := range p.candidates {
|
||||
for _, cc := range c.Child {
|
||||
if cc, ok := cc.(*Element); ok &&
|
||||
spaceMatch(f.space, cc.Space) &&
|
||||
f.tag == cc.Tag {
|
||||
p.scratch = append(p.scratch, c)
|
||||
}
|
||||
}
|
||||
}
|
||||
p.candidates, p.scratch = p.scratch, p.candidates[0:0]
|
||||
}
|
||||
|
||||
// filterChildText filters the candidate list for elements having
|
||||
// a child element with the specified tag and text.
|
||||
type filterChildText struct {
|
||||
space, tag, text string
|
||||
}
|
||||
|
||||
func newFilterChildText(str, text string) *filterChildText {
|
||||
s, l := spaceDecompose(str)
|
||||
return &filterChildText{s, l, text}
|
||||
}
|
||||
|
||||
func (f *filterChildText) apply(p *pather) {
|
||||
for _, c := range p.candidates {
|
||||
for _, cc := range c.Child {
|
||||
if cc, ok := cc.(*Element); ok &&
|
||||
spaceMatch(f.space, cc.Space) &&
|
||||
f.tag == cc.Tag &&
|
||||
f.text == cc.Text() {
|
||||
p.scratch = append(p.scratch, c)
|
||||
}
|
||||
}
|
||||
}
|
||||
p.candidates, p.scratch = p.scratch, p.candidates[0:0]
|
||||
}
|
3
vendor/github.com/crewjam/saml/.gitignore
generated
vendored
Normal file
3
vendor/github.com/crewjam/saml/.gitignore
generated
vendored
Normal file
@ -0,0 +1,3 @@
|
||||
coverage.out
|
||||
coverage.html
|
||||
vendor/
|
98
vendor/github.com/crewjam/saml/Gopkg.lock
generated
vendored
Normal file
98
vendor/github.com/crewjam/saml/Gopkg.lock
generated
vendored
Normal file
@ -0,0 +1,98 @@
|
||||
# This file is autogenerated, do not edit; changes may be undone by the next 'dep ensure'.
|
||||
|
||||
|
||||
[[projects]]
|
||||
name = "github.com/beevik/etree"
|
||||
packages = ["."]
|
||||
revision = "9d7e8feddccb4ed1b8afb54e368bd323d2ff652c"
|
||||
version = "v1.0.1"
|
||||
|
||||
[[projects]]
|
||||
name = "github.com/crewjam/saml"
|
||||
packages = [
|
||||
".",
|
||||
"logger",
|
||||
"samlidp",
|
||||
"samlsp",
|
||||
"testsaml",
|
||||
"xmlenc"
|
||||
]
|
||||
revision = "6b5dd2d26974f7f5e59132ef5921fab7993794d7"
|
||||
version = "0.2.0"
|
||||
|
||||
[[projects]]
|
||||
branch = "master"
|
||||
name = "github.com/dchest/uniuri"
|
||||
packages = ["."]
|
||||
revision = "8902c56451e9b58ff940bbe5fec35d5f9c04584a"
|
||||
|
||||
[[projects]]
|
||||
name = "github.com/dgrijalva/jwt-go"
|
||||
packages = ["."]
|
||||
revision = "06ea1031745cb8b3dab3f6a236daf2b0aa468b7e"
|
||||
version = "v3.2.0"
|
||||
|
||||
[[projects]]
|
||||
name = "github.com/jonboulle/clockwork"
|
||||
packages = ["."]
|
||||
revision = "2eee05ed794112d45db504eb05aa693efd2b8b09"
|
||||
version = "v0.1.0"
|
||||
|
||||
[[projects]]
|
||||
name = "github.com/kr/pretty"
|
||||
packages = ["."]
|
||||
revision = "73f6ac0b30a98e433b289500d779f50c1a6f0712"
|
||||
version = "v0.1.0"
|
||||
|
||||
[[projects]]
|
||||
name = "github.com/kr/text"
|
||||
packages = ["."]
|
||||
revision = "e2ffdb16a802fe2bb95e2e35ff34f0e53aeef34f"
|
||||
version = "v0.1.0"
|
||||
|
||||
[[projects]]
|
||||
branch = "master"
|
||||
name = "github.com/russellhaering/goxmldsig"
|
||||
packages = [
|
||||
".",
|
||||
"etreeutils",
|
||||
"types"
|
||||
]
|
||||
revision = "7acd5e4a6ef74fe1b082c20f119556adf70c3944"
|
||||
|
||||
[[projects]]
|
||||
name = "github.com/zenazn/goji"
|
||||
packages = [
|
||||
".",
|
||||
"bind",
|
||||
"graceful",
|
||||
"graceful/listener",
|
||||
"web",
|
||||
"web/middleware",
|
||||
"web/mutil"
|
||||
]
|
||||
revision = "64eb34159fe53473206c2b3e70fe396a639452f2"
|
||||
version = "v1.0"
|
||||
|
||||
[[projects]]
|
||||
branch = "master"
|
||||
name = "golang.org/x/crypto"
|
||||
packages = [
|
||||
"bcrypt",
|
||||
"blowfish",
|
||||
"ripemd160"
|
||||
]
|
||||
revision = "c126467f60eb25f8f27e5a981f32a87e3965053f"
|
||||
|
||||
[[projects]]
|
||||
branch = "v1"
|
||||
name = "gopkg.in/check.v1"
|
||||
packages = ["."]
|
||||
revision = "788fd78401277ebd861206a03c884797c6ec5541"
|
||||
|
||||
[solve-meta]
|
||||
analyzer-name = "dep"
|
||||
analyzer-version = 1
|
||||
inputs-digest = "e95ae53367b806651c34d425c22f40bade70c9db018c9b619b37f4c8405acb65"
|
||||
solver-name = "gps-cdcl"
|
||||
solver-version = 1
|
70
vendor/github.com/crewjam/saml/Gopkg.toml
generated
vendored
Normal file
70
vendor/github.com/crewjam/saml/Gopkg.toml
generated
vendored
Normal file
@ -0,0 +1,70 @@
|
||||
# This file is autogenerated, do not edit; changes may be undone by the next 'dep ensure'.
|
||||
|
||||
|
||||
[[projects]]
|
||||
name = "github.com/beevik/etree"
|
||||
packages = ["."]
|
||||
revision = "15a30b44cfd6c5a16a7ddfe271bf146aaf2d3195"
|
||||
version = "v1.0.0"
|
||||
|
||||
[[projects]]
|
||||
branch = "master"
|
||||
name = "github.com/dchest/uniuri"
|
||||
packages = ["."]
|
||||
revision = "8902c56451e9b58ff940bbe5fec35d5f9c04584a"
|
||||
|
||||
[[projects]]
|
||||
name = "github.com/dgrijalva/jwt-go"
|
||||
packages = ["."]
|
||||
revision = "d2709f9f1f31ebcda9651b03077758c1f3a0018c"
|
||||
version = "v3.0.0"
|
||||
|
||||
[[projects]]
|
||||
name = "github.com/jonboulle/clockwork"
|
||||
packages = ["."]
|
||||
revision = "2eee05ed794112d45db504eb05aa693efd2b8b09"
|
||||
version = "v0.1.0"
|
||||
|
||||
[[projects]]
|
||||
branch = "master"
|
||||
name = "github.com/kr/pretty"
|
||||
packages = ["."]
|
||||
revision = "cfb55aafdaf3ec08f0db22699ab822c50091b1c4"
|
||||
|
||||
[[projects]]
|
||||
branch = "master"
|
||||
name = "github.com/kr/text"
|
||||
packages = ["."]
|
||||
revision = "7cafcd837844e784b526369c9bce262804aebc60"
|
||||
|
||||
[[projects]]
|
||||
branch = "master"
|
||||
name = "github.com/russellhaering/goxmldsig"
|
||||
packages = [".","etreeutils","types"]
|
||||
revision = "b7efc6231e45b10bfd779852831c8bb59b350ec5"
|
||||
|
||||
[[projects]]
|
||||
name = "github.com/zenazn/goji"
|
||||
packages = [".","bind","graceful","graceful/listener","web","web/middleware","web/mutil"]
|
||||
revision = "64eb34159fe53473206c2b3e70fe396a639452f2"
|
||||
version = "v1.0"
|
||||
|
||||
[[projects]]
|
||||
branch = "master"
|
||||
name = "golang.org/x/crypto"
|
||||
packages = ["bcrypt","blowfish","ripemd160"]
|
||||
revision = "847319b7fc94cab682988f93da778204da164588"
|
||||
|
||||
[[projects]]
|
||||
branch = "v1"
|
||||
name = "gopkg.in/check.v1"
|
||||
packages = ["."]
|
||||
revision = "20d25e2804050c1cd24a7eea1e7a6447dd0e74ec"
|
||||
|
||||
[solve-meta]
|
||||
analyzer-name = "dep"
|
||||
analyzer-version = 1
|
||||
inputs-digest = "253ec289f823a19c6473233e4934b31f5e623b0fb3136183b129cb652a5685c2"
|
||||
solver-name = "gps-cdcl"
|
||||
solver-version = 1
|
||||
|
23
vendor/github.com/crewjam/saml/LICENSE
generated
vendored
Normal file
23
vendor/github.com/crewjam/saml/LICENSE
generated
vendored
Normal file
@ -0,0 +1,23 @@
|
||||
Copyright (c) 2015, Ross Kinder
|
||||
All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without modification,
|
||||
are permitted provided that the following conditions are met:
|
||||
|
||||
1. Redistributions of source code must retain the above copyright notice, this
|
||||
list of conditions and the following disclaimer.
|
||||
|
||||
2. Redistributions in binary form must reproduce the above copyright notice,
|
||||
this list of conditions and the following disclaimer in the documentation
|
||||
and/or other materials provided with the distribution.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
|
||||
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
||||
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
||||
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
||||
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
||||
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
||||
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
166
vendor/github.com/crewjam/saml/README.md
generated
vendored
Normal file
166
vendor/github.com/crewjam/saml/README.md
generated
vendored
Normal file
@ -0,0 +1,166 @@
|
||||
# SAML
|
||||
[](http://godoc.org/github.com/crewjam/saml)
|
||||
|
||||
[](https://travis-ci.org/crewjam/saml)
|
||||
|
||||
Package saml contains a partial implementation of the SAML standard in golang.
|
||||
SAML is a standard for identity federation, i.e. either allowing a third party to authenticate your users or allowing third parties to rely on us to authenticate their users.
|
||||
|
||||
## Introduction
|
||||
|
||||
In SAML parlance an **Identity Provider** (IDP) is a service that knows how to authenticate users. A **Service Provider** (SP) is a service that delegates authentication to an IDP. If you are building a service where users log in with someone else's credentials, then you are a **Service Provider**. This package supports implementing both service providers and identity providers.
|
||||
|
||||
The core package contains the implementation of SAML. The package samlsp provides helper middleware suitable for use in Service Provider applications. The package samlidp provides a rudimentary IDP service that is useful for testing or as a starting point for other integrations.
|
||||
|
||||
## Breaking Changes
|
||||
|
||||
Note: between version 0.2.0 and the current master include changes to the API
|
||||
that will break your existing code a little.
|
||||
|
||||
This change turned some fields from pointers to a single optional struct into
|
||||
the more correct slice of struct, and to pluralize the field name. For example,
|
||||
`IDPSSODescriptor *IDPSSODescriptor` has become
|
||||
`IDPSSODescriptors []IDPSSODescriptor`. This more accurately reflects the
|
||||
standard.
|
||||
|
||||
The struct `Metadata` has been renamed to `EntityDescriptor`. In 0.2.0 and before,
|
||||
every struct derived from the standard has the same name as in the standard,
|
||||
*except* for `Metadata` which should always have been called `EntityDescriptor`.
|
||||
|
||||
In various places `url.URL` is now used where `string` was used <= version 0.1.0.
|
||||
|
||||
In various places where keys and certificates were modeled as `string`
|
||||
<= version 0.1.0 (what was I thinking?!) they are now modeled as
|
||||
`*rsa.PrivateKey`, `*x509.Certificate`, or `crypto.PrivateKey` as appropriate.
|
||||
|
||||
## Getting Started as a Service Provider
|
||||
|
||||
Let us assume we have a simple web application to protect. We'll modify this application so it uses SAML to authenticate users.
|
||||
```golang
|
||||
package main
|
||||
|
||||
import "net/http"
|
||||
|
||||
func hello(w http.ResponseWriter, r *http.Request) {
|
||||
fmt.Fprintf(w, "Hello, World!")
|
||||
}
|
||||
|
||||
func main() {
|
||||
app := http.HandlerFunc(hello)
|
||||
http.Handle("/hello", app)
|
||||
http.ListenAndServe(":8000", nil)
|
||||
}
|
||||
```
|
||||
Each service provider must have an self-signed X.509 key pair established. You can generate your own with something like this:
|
||||
|
||||
openssl req -x509 -newkey rsa:2048 -keyout myservice.key -out myservice.cert -days 365 -nodes -subj "/CN=myservice.example.com"
|
||||
|
||||
We will use `samlsp.Middleware` to wrap the endpoint we want to protect. Middleware provides both an `http.Handler` to serve the SAML specific URLs **and** a set of wrappers to require the user to be logged in. We also provide the URL where the service provider can fetch the metadata from the IDP at startup. In our case, we'll use [testshib.org](https://www.testshib.org/), an identity provider designed for testing.
|
||||
|
||||
```golang
|
||||
package main
|
||||
|
||||
import (
|
||||
"crypto/rsa"
|
||||
"crypto/tls"
|
||||
"crypto/x509"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"net/url"
|
||||
|
||||
"github.com/crewjam/saml/samlsp"
|
||||
)
|
||||
|
||||
func hello(w http.ResponseWriter, r *http.Request) {
|
||||
fmt.Fprintf(w, "Hello, %s!", samlsp.Token(r.Context()).Attributes.Get("cn"))
|
||||
}
|
||||
|
||||
func main() {
|
||||
keyPair, err := tls.LoadX509KeyPair("myservice.cert", "myservice.key")
|
||||
if err != nil {
|
||||
panic(err) // TODO handle error
|
||||
}
|
||||
keyPair.Leaf, err = x509.ParseCertificate(keyPair.Certificate[0])
|
||||
if err != nil {
|
||||
panic(err) // TODO handle error
|
||||
}
|
||||
|
||||
idpMetadataURL, err := url.Parse("https://www.testshib.org/metadata/testshib-providers.xml")
|
||||
if err != nil {
|
||||
panic(err) // TODO handle error
|
||||
}
|
||||
|
||||
rootURL, err := url.Parse("http://localhost:8000")
|
||||
if err != nil {
|
||||
panic(err) // TODO handle error
|
||||
}
|
||||
|
||||
samlSP, _ := samlsp.New(samlsp.Options{
|
||||
URL: *rootURL,
|
||||
Key: keyPair.PrivateKey.(*rsa.PrivateKey),
|
||||
Certificate: keyPair.Leaf,
|
||||
IDPMetadataURL: idpMetadataURL,
|
||||
})
|
||||
app := http.HandlerFunc(hello)
|
||||
http.Handle("/hello", samlSP.RequireAccount(app))
|
||||
http.Handle("/saml/", samlSP)
|
||||
http.ListenAndServe(":8000", nil)
|
||||
}
|
||||
```
|
||||
|
||||
Next we'll have to register our service provider with the identity provider to establish trust from the service provider to the IDP. For [testshib.org](https://www.testshib.org/), you can do something like:
|
||||
|
||||
mdpath=saml-test-$USER-$HOST.xml
|
||||
curl localhost:8000/saml/metadata > $mdpath
|
||||
|
||||
Navigate to https://www.testshib.org/register.html and upload the file you fetched.
|
||||
|
||||
Now you should be able to authenticate. The flow should look like this:
|
||||
|
||||
1. You browse to `localhost:8000/hello`
|
||||
|
||||
1. The middleware redirects you to `https://idp.testshib.org/idp/profile/SAML2/Redirect/SSO`
|
||||
|
||||
1. testshib.org prompts you for a username and password.
|
||||
|
||||
1. testshib.org returns you an HTML document which contains an HTML form setup to POST to `localhost:8000/saml/acs`. The form is automatically submitted if you have javascript enabled.
|
||||
|
||||
1. The local service validates the response, issues a session cookie, and redirects you to the original URL, `localhost:8000/hello`.
|
||||
|
||||
1. This time when `localhost:8000/hello` is requested there is a valid session and so the main content is served.
|
||||
|
||||
## Getting Started as an Identity Provider
|
||||
|
||||
Please see `examples/idp/` for a substantially complete example of how to use the library and helpers to be an identity provider.
|
||||
|
||||
## Support
|
||||
|
||||
The SAML standard is huge and complex with many dark corners and strange, unused features. This package implements the most commonly used subset of these features required to provide a single sign on experience. The package supports at least the subset of SAML known as [interoperable SAML](http://saml2int.org).
|
||||
|
||||
This package supports the **Web SSO** profile. Message flows from the service provider to the IDP are supported using the **HTTP Redirect** binding and the **HTTP POST** binding. Message flows from the IDP to the service provider are supported via the **HTTP POST** binding.
|
||||
|
||||
The package supports signed and encrypted SAML assertions. It does not support signed or encrypted requests.
|
||||
|
||||
## RelayState
|
||||
|
||||
The *RelayState* parameter allows you to pass user state information across the authentication flow. The most common use for this is to allow a user to request a deep link into your site, be redirected through the SAML login flow, and upon successful completion, be directed to the originally requested link, rather than the root.
|
||||
|
||||
Unfortunately, *RelayState* is less useful than it could be. Firstly, it is **not** authenticated, so anything you supply must be signed to avoid XSS or CSRF. Secondly, it is limited to 80 bytes in length, which precludes signing. (See section 3.6.3.1 of SAMLProfiles.)
|
||||
|
||||
## References
|
||||
|
||||
The SAML specification is a collection of PDFs (sadly):
|
||||
|
||||
- [SAMLCore](http://docs.oasis-open.org/security/saml/v2.0/saml-core-2.0-os.pdf) defines data types.
|
||||
|
||||
- [SAMLBindings](http://docs.oasis-open.org/security/saml/v2.0/saml-bindings-2.0-os.pdf) defines the details of the HTTP requests in play.
|
||||
|
||||
- [SAMLProfiles](http://docs.oasis-open.org/security/saml/v2.0/saml-profiles-2.0-os.pdf) describes data flows.
|
||||
|
||||
- [SAMLConformance](http://docs.oasis-open.org/security/saml/v2.0/saml-conformance-2.0-os.pdf) includes a support matrix for various parts of the protocol.
|
||||
|
||||
[TestShib](https://www.testshib.org/) is a testing ground for SAML service and identity providers.
|
||||
|
||||
## Security Issues
|
||||
|
||||
Please do not report security issues in the issue tracker. Rather, please contact me directly at ross@kndr.org ([PGP Key `78B6038B3B9DFB88`](https://keybase.io/crewjam)).
|
128
vendor/github.com/crewjam/saml/duration.go
generated
vendored
Normal file
128
vendor/github.com/crewjam/saml/duration.go
generated
vendored
Normal file
@ -0,0 +1,128 @@
|
||||
package saml
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"regexp"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
// Duration is a time.Duration that uses the xsd:duration format for text
|
||||
// marshalling and unmarshalling.
|
||||
type Duration time.Duration
|
||||
|
||||
// MarshalText implements the encoding.TextMarshaler interface.
|
||||
func (d Duration) MarshalText() ([]byte, error) {
|
||||
if d == 0 {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
out := "PT"
|
||||
if d < 0 {
|
||||
d *= -1
|
||||
out = "-" + out
|
||||
}
|
||||
|
||||
h := time.Duration(d) / time.Hour
|
||||
m := time.Duration(d) % time.Hour / time.Minute
|
||||
s := time.Duration(d) % time.Minute / time.Second
|
||||
ns := time.Duration(d) % time.Second
|
||||
if h > 0 {
|
||||
out += fmt.Sprintf("%dH", h)
|
||||
}
|
||||
if m > 0 {
|
||||
out += fmt.Sprintf("%dM", m)
|
||||
}
|
||||
if s > 0 || ns > 0 {
|
||||
out += fmt.Sprintf("%d", s)
|
||||
if ns > 0 {
|
||||
out += strings.TrimRight(fmt.Sprintf(".%09d", ns), "0")
|
||||
}
|
||||
out += "S"
|
||||
}
|
||||
|
||||
return []byte(out), nil
|
||||
}
|
||||
|
||||
const (
|
||||
day = 24 * time.Hour
|
||||
month = 30 * day // Assumed to be 30 days.
|
||||
year = 365 * day // Assumed to be non-leap year.
|
||||
)
|
||||
|
||||
var (
|
||||
durationRegexp = regexp.MustCompile(`^(-?)P(?:(\d+)Y)?(?:(\d+)M)?(?:(\d+)D)?(?:T(.+))?$`)
|
||||
durationTimeRegexp = regexp.MustCompile(`^(?:(\d+)H)?(?:(\d+)M)?(?:(\d+(?:\.\d+)?)S)?$`)
|
||||
)
|
||||
|
||||
// UnmarshalText implements the encoding.TextUnmarshaler interface.
|
||||
func (d *Duration) UnmarshalText(text []byte) error {
|
||||
if text == nil {
|
||||
*d = 0
|
||||
return nil
|
||||
}
|
||||
|
||||
var (
|
||||
out time.Duration
|
||||
sign time.Duration = 1
|
||||
)
|
||||
match := durationRegexp.FindStringSubmatch(string(text))
|
||||
if match == nil || strings.Join(match[2:6], "") == "" {
|
||||
return fmt.Errorf("invalid duration (%s)", text)
|
||||
}
|
||||
if match[1] == "-" {
|
||||
sign = -1
|
||||
}
|
||||
if match[2] != "" {
|
||||
y, err := strconv.Atoi(match[2])
|
||||
if err != nil {
|
||||
return fmt.Errorf("invalid duration years (%s): %s", text, err)
|
||||
}
|
||||
out += time.Duration(y) * year
|
||||
}
|
||||
if match[3] != "" {
|
||||
m, err := strconv.Atoi(match[3])
|
||||
if err != nil {
|
||||
return fmt.Errorf("invalid duration months (%s): %s", text, err)
|
||||
}
|
||||
out += time.Duration(m) * month
|
||||
}
|
||||
if match[4] != "" {
|
||||
d, err := strconv.Atoi(match[4])
|
||||
if err != nil {
|
||||
return fmt.Errorf("invalid duration days (%s): %s", text, err)
|
||||
}
|
||||
out += time.Duration(d) * day
|
||||
}
|
||||
if match[5] != "" {
|
||||
match := durationTimeRegexp.FindStringSubmatch(match[5])
|
||||
if match == nil {
|
||||
return fmt.Errorf("invalid duration (%s)", text)
|
||||
}
|
||||
if match[1] != "" {
|
||||
h, err := strconv.Atoi(match[1])
|
||||
if err != nil {
|
||||
return fmt.Errorf("invalid duration hours (%s): %s", text, err)
|
||||
}
|
||||
out += time.Duration(h) * time.Hour
|
||||
}
|
||||
if match[2] != "" {
|
||||
m, err := strconv.Atoi(match[2])
|
||||
if err != nil {
|
||||
return fmt.Errorf("invalid duration minutes (%s): %s", text, err)
|
||||
}
|
||||
out += time.Duration(m) * time.Minute
|
||||
}
|
||||
if match[3] != "" {
|
||||
s, err := strconv.ParseFloat(match[3], 64)
|
||||
if err != nil {
|
||||
return fmt.Errorf("invalid duration seconds (%s): %s", text, err)
|
||||
}
|
||||
out += time.Duration(s * float64(time.Second))
|
||||
}
|
||||
}
|
||||
|
||||
*d = Duration(sign * out)
|
||||
return nil
|
||||
}
|
952
vendor/github.com/crewjam/saml/identity_provider.go
generated
vendored
Normal file
952
vendor/github.com/crewjam/saml/identity_provider.go
generated
vendored
Normal file
@ -0,0 +1,952 @@
|
||||
package saml
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"compress/flate"
|
||||
"crypto"
|
||||
"crypto/tls"
|
||||
"crypto/x509"
|
||||
"encoding/base64"
|
||||
"encoding/xml"
|
||||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"os"
|
||||
"regexp"
|
||||
"strconv"
|
||||
"text/template"
|
||||
"time"
|
||||
|
||||
"github.com/beevik/etree"
|
||||
"github.com/crewjam/saml/logger"
|
||||
"github.com/crewjam/saml/xmlenc"
|
||||
dsig "github.com/russellhaering/goxmldsig"
|
||||
)
|
||||
|
||||
// Session represents a user session. It is returned by the
|
||||
// SessionProvider implementation's GetSession method. Fields here
|
||||
// are used to set fields in the SAML assertion.
|
||||
type Session struct {
|
||||
ID string
|
||||
CreateTime time.Time
|
||||
ExpireTime time.Time
|
||||
Index string
|
||||
|
||||
NameID string
|
||||
Groups []string
|
||||
UserName string
|
||||
UserEmail string
|
||||
UserCommonName string
|
||||
UserSurname string
|
||||
UserGivenName string
|
||||
}
|
||||
|
||||
// SessionProvider is an interface used by IdentityProvider to determine the
|
||||
// Session associated with a request. For an example implementation, see
|
||||
// GetSession in the samlidp package.
|
||||
type SessionProvider interface {
|
||||
// GetSession returns the remote user session associated with the http.Request.
|
||||
//
|
||||
// If (and only if) the request is not associated with a session then GetSession
|
||||
// must complete the HTTP request and return nil.
|
||||
GetSession(w http.ResponseWriter, r *http.Request, req *IdpAuthnRequest) *Session
|
||||
}
|
||||
|
||||
// ServiceProviderProvider is an interface used by IdentityProvider to look up
|
||||
// service provider metadata for a request.
|
||||
type ServiceProviderProvider interface {
|
||||
// GetServiceProvider returns the Service Provider metadata for the
|
||||
// service provider ID, which is typically the service provider's
|
||||
// metadata URL. If an appropriate service provider cannot be found then
|
||||
// the returned error must be os.ErrNotExist.
|
||||
GetServiceProvider(r *http.Request, serviceProviderID string) (*EntityDescriptor, error)
|
||||
}
|
||||
|
||||
// AssertionMaker is an interface used by IdentityProvider to construct the
|
||||
// assertion for a request. The default implementation is DefaultAssertionMaker,
|
||||
// which is used if not AssertionMaker is specified.
|
||||
type AssertionMaker interface {
|
||||
// MakeAssertion constructs an assertion from session and the request and
|
||||
// assigns it to req.Assertion.
|
||||
MakeAssertion(req *IdpAuthnRequest, session *Session) error
|
||||
}
|
||||
|
||||
// IdentityProvider implements the SAML Identity Provider role (IDP).
|
||||
//
|
||||
// An identity provider receives SAML assertion requests and responds
|
||||
// with SAML Assertions.
|
||||
//
|
||||
// You must provide a keypair that is used to
|
||||
// sign assertions.
|
||||
//
|
||||
// You must provide an implementation of ServiceProviderProvider which
|
||||
// returns
|
||||
//
|
||||
// You must provide an implementation of the SessionProvider which
|
||||
// handles the actual authentication (i.e. prompting for a username
|
||||
// and password).
|
||||
type IdentityProvider struct {
|
||||
Key crypto.PrivateKey
|
||||
Logger logger.Interface
|
||||
Certificate *x509.Certificate
|
||||
Intermediates []*x509.Certificate
|
||||
MetadataURL url.URL
|
||||
SSOURL url.URL
|
||||
LogoutURL url.URL
|
||||
ServiceProviderProvider ServiceProviderProvider
|
||||
SessionProvider SessionProvider
|
||||
AssertionMaker AssertionMaker
|
||||
SignatureMethod string
|
||||
}
|
||||
|
||||
// Metadata returns the metadata structure for this identity provider.
|
||||
func (idp *IdentityProvider) Metadata() *EntityDescriptor {
|
||||
certStr := base64.StdEncoding.EncodeToString(idp.Certificate.Raw)
|
||||
|
||||
ed := &EntityDescriptor{
|
||||
EntityID: idp.MetadataURL.String(),
|
||||
ValidUntil: TimeNow().Add(DefaultValidDuration),
|
||||
CacheDuration: DefaultValidDuration,
|
||||
IDPSSODescriptors: []IDPSSODescriptor{
|
||||
IDPSSODescriptor{
|
||||
SSODescriptor: SSODescriptor{
|
||||
RoleDescriptor: RoleDescriptor{
|
||||
ProtocolSupportEnumeration: "urn:oasis:names:tc:SAML:2.0:protocol",
|
||||
KeyDescriptors: []KeyDescriptor{
|
||||
{
|
||||
Use: "signing",
|
||||
KeyInfo: KeyInfo{
|
||||
Certificate: certStr,
|
||||
},
|
||||
},
|
||||
{
|
||||
Use: "encryption",
|
||||
KeyInfo: KeyInfo{
|
||||
Certificate: certStr,
|
||||
},
|
||||
EncryptionMethods: []EncryptionMethod{
|
||||
{Algorithm: "http://www.w3.org/2001/04/xmlenc#aes128-cbc"},
|
||||
{Algorithm: "http://www.w3.org/2001/04/xmlenc#aes192-cbc"},
|
||||
{Algorithm: "http://www.w3.org/2001/04/xmlenc#aes256-cbc"},
|
||||
{Algorithm: "http://www.w3.org/2001/04/xmlenc#rsa-oaep-mgf1p"},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
NameIDFormats: []NameIDFormat{NameIDFormat("urn:oasis:names:tc:SAML:2.0:nameid-format:transient")},
|
||||
},
|
||||
SingleSignOnServices: []Endpoint{
|
||||
{
|
||||
Binding: HTTPRedirectBinding,
|
||||
Location: idp.SSOURL.String(),
|
||||
},
|
||||
{
|
||||
Binding: HTTPPostBinding,
|
||||
Location: idp.SSOURL.String(),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
if idp.LogoutURL.String() != "" {
|
||||
ed.IDPSSODescriptors[0].SSODescriptor.SingleLogoutServices = []Endpoint{
|
||||
{
|
||||
Binding: HTTPRedirectBinding,
|
||||
Location: idp.LogoutURL.String(),
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
return ed
|
||||
}
|
||||
|
||||
// Handler returns an http.Handler that serves the metadata and SSO
|
||||
// URLs
|
||||
func (idp *IdentityProvider) Handler() http.Handler {
|
||||
mux := http.NewServeMux()
|
||||
mux.HandleFunc(idp.MetadataURL.Path, idp.ServeMetadata)
|
||||
mux.HandleFunc(idp.SSOURL.Path, idp.ServeSSO)
|
||||
return mux
|
||||
}
|
||||
|
||||
// ServeMetadata is an http.HandlerFunc that serves the IDP metadata
|
||||
func (idp *IdentityProvider) ServeMetadata(w http.ResponseWriter, r *http.Request) {
|
||||
buf, _ := xml.MarshalIndent(idp.Metadata(), "", " ")
|
||||
w.Header().Set("Content-Type", "application/samlmetadata+xml")
|
||||
w.Write(buf)
|
||||
}
|
||||
|
||||
// ServeSSO handles SAML auth requests.
|
||||
//
|
||||
// When it gets a request for a user that does not have a valid session,
|
||||
// then it prompts the user via XXX.
|
||||
//
|
||||
// If the session already exists, then it produces a SAML assertion and
|
||||
// returns an HTTP response according to the specified binding. The
|
||||
// only supported binding right now is the HTTP-POST binding which returns
|
||||
// an HTML form in the appropriate format with Javascript to automatically
|
||||
// submit that form the to service provider's Assertion Customer Service
|
||||
// endpoint.
|
||||
//
|
||||
// If the SAML request is invalid or cannot be verified a simple StatusBadRequest
|
||||
// response is sent.
|
||||
//
|
||||
// If the assertion cannot be created or returned, a StatusInternalServerError
|
||||
// response is sent.
|
||||
func (idp *IdentityProvider) ServeSSO(w http.ResponseWriter, r *http.Request) {
|
||||
req, err := NewIdpAuthnRequest(idp, r)
|
||||
if err != nil {
|
||||
idp.Logger.Printf("failed to parse request: %s", err)
|
||||
http.Error(w, http.StatusText(http.StatusBadRequest), http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
if err := req.Validate(); err != nil {
|
||||
idp.Logger.Printf("failed to validate request: %s", err)
|
||||
http.Error(w, http.StatusText(http.StatusBadRequest), http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
// TODO(ross): we must check that the request ID has not been previously
|
||||
// issued.
|
||||
|
||||
session := idp.SessionProvider.GetSession(w, r, req)
|
||||
if session == nil {
|
||||
return
|
||||
}
|
||||
|
||||
assertionMaker := idp.AssertionMaker
|
||||
if assertionMaker == nil {
|
||||
assertionMaker = DefaultAssertionMaker{}
|
||||
}
|
||||
if err := assertionMaker.MakeAssertion(req, session); err != nil {
|
||||
idp.Logger.Printf("failed to make assertion: %s", err)
|
||||
http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
if err := req.WriteResponse(w); err != nil {
|
||||
idp.Logger.Printf("failed to write response: %s", err)
|
||||
http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// ServeIDPInitiated handes an IDP-initiated authorization request. Requests of this
|
||||
// type require us to know a registered service provider and (optionally) the RelayState
|
||||
// that will be passed to the application.
|
||||
func (idp *IdentityProvider) ServeIDPInitiated(w http.ResponseWriter, r *http.Request, serviceProviderID string, relayState string) {
|
||||
req := &IdpAuthnRequest{
|
||||
IDP: idp,
|
||||
HTTPRequest: r,
|
||||
RelayState: relayState,
|
||||
Now: TimeNow(),
|
||||
}
|
||||
|
||||
session := idp.SessionProvider.GetSession(w, r, req)
|
||||
if session == nil {
|
||||
// If GetSession returns nil, it must have written an HTTP response, per the interface
|
||||
// (this is probably because it drew a login form or something)
|
||||
return
|
||||
}
|
||||
|
||||
var err error
|
||||
req.ServiceProviderMetadata, err = idp.ServiceProviderProvider.GetServiceProvider(r, serviceProviderID)
|
||||
if err == os.ErrNotExist {
|
||||
idp.Logger.Printf("cannot find service provider: %s", serviceProviderID)
|
||||
http.Error(w, http.StatusText(http.StatusNotFound), http.StatusNotFound)
|
||||
return
|
||||
} else if err != nil {
|
||||
idp.Logger.Printf("cannot find service provider %s: %v", serviceProviderID, err)
|
||||
http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
// find an ACS endpoint that we can use
|
||||
for _, spssoDescriptor := range req.ServiceProviderMetadata.SPSSODescriptors {
|
||||
for _, endpoint := range spssoDescriptor.AssertionConsumerServices {
|
||||
if endpoint.Binding == HTTPPostBinding {
|
||||
req.ACSEndpoint = &endpoint
|
||||
req.SPSSODescriptor = &spssoDescriptor
|
||||
break
|
||||
}
|
||||
}
|
||||
if req.ACSEndpoint != nil {
|
||||
break
|
||||
}
|
||||
}
|
||||
if req.ACSEndpoint == nil {
|
||||
idp.Logger.Printf("saml metadata does not contain an Assertion Customer Service url")
|
||||
http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
assertionMaker := idp.AssertionMaker
|
||||
if assertionMaker == nil {
|
||||
assertionMaker = DefaultAssertionMaker{}
|
||||
}
|
||||
if err := assertionMaker.MakeAssertion(req, session); err != nil {
|
||||
idp.Logger.Printf("failed to make assertion: %s", err)
|
||||
http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
if err := req.WriteResponse(w); err != nil {
|
||||
idp.Logger.Printf("failed to write response: %s", err)
|
||||
http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// IdpAuthnRequest is used by IdentityProvider to handle a single authentication request.
|
||||
type IdpAuthnRequest struct {
|
||||
IDP *IdentityProvider
|
||||
HTTPRequest *http.Request
|
||||
RelayState string
|
||||
RequestBuffer []byte
|
||||
Request AuthnRequest
|
||||
ServiceProviderMetadata *EntityDescriptor
|
||||
SPSSODescriptor *SPSSODescriptor
|
||||
ACSEndpoint *IndexedEndpoint
|
||||
Assertion *Assertion
|
||||
AssertionEl *etree.Element
|
||||
ResponseEl *etree.Element
|
||||
Now time.Time
|
||||
}
|
||||
|
||||
// NewIdpAuthnRequest returns a new IdpAuthnRequest for the given HTTP request to the authorization
|
||||
// service.
|
||||
func NewIdpAuthnRequest(idp *IdentityProvider, r *http.Request) (*IdpAuthnRequest, error) {
|
||||
req := &IdpAuthnRequest{
|
||||
IDP: idp,
|
||||
HTTPRequest: r,
|
||||
Now: TimeNow(),
|
||||
}
|
||||
|
||||
switch r.Method {
|
||||
case "GET":
|
||||
compressedRequest, err := base64.StdEncoding.DecodeString(r.URL.Query().Get("SAMLRequest"))
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("cannot decode request: %s", err)
|
||||
}
|
||||
req.RequestBuffer, err = ioutil.ReadAll(flate.NewReader(bytes.NewReader(compressedRequest)))
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("cannot decompress request: %s", err)
|
||||
}
|
||||
req.RelayState = r.URL.Query().Get("RelayState")
|
||||
case "POST":
|
||||
if err := r.ParseForm(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
var err error
|
||||
req.RequestBuffer, err = base64.StdEncoding.DecodeString(r.PostForm.Get("SAMLRequest"))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
req.RelayState = r.PostForm.Get("RelayState")
|
||||
default:
|
||||
return nil, fmt.Errorf("method not allowed")
|
||||
}
|
||||
return req, nil
|
||||
}
|
||||
|
||||
// Validate checks that the authentication request is valid and assigns
|
||||
// the AuthnRequest and Metadata properties. Returns a non-nil error if the
|
||||
// request is not valid.
|
||||
func (req *IdpAuthnRequest) Validate() error {
|
||||
if err := xml.Unmarshal(req.RequestBuffer, &req.Request); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// We always have exactly one IDP SSO descriptor
|
||||
if len(req.IDP.Metadata().IDPSSODescriptors) != 1 {
|
||||
panic("expected exactly one IDP SSO descriptor in IDP metadata")
|
||||
}
|
||||
idpSsoDescriptor := req.IDP.Metadata().IDPSSODescriptors[0]
|
||||
|
||||
// TODO(ross): support signed authn requests
|
||||
// For now we do the safe thing and fail in the case where we think
|
||||
// requests might be signed.
|
||||
if idpSsoDescriptor.WantAuthnRequestsSigned != nil && *idpSsoDescriptor.WantAuthnRequestsSigned {
|
||||
return fmt.Errorf("Authn request signature checking is not currently supported")
|
||||
}
|
||||
|
||||
// In http://docs.oasis-open.org/security/saml/v2.0/saml-bindings-2.0-os.pdf §3.4.5.2
|
||||
// we get a description of the Destination attribute:
|
||||
//
|
||||
// If the message is signed, the Destination XML attribute in the root SAML
|
||||
// element of the protocol message MUST contain the URL to which the sender
|
||||
// has instructed the user agent to deliver the message. The recipient MUST
|
||||
// then verify that the value matches the location at which the message has
|
||||
// been received.
|
||||
//
|
||||
// We require the destination be correct either (a) if signing is enabled or
|
||||
// (b) if it was provided.
|
||||
mustHaveDestination := idpSsoDescriptor.WantAuthnRequestsSigned != nil && *idpSsoDescriptor.WantAuthnRequestsSigned
|
||||
mustHaveDestination = mustHaveDestination || req.Request.Destination != ""
|
||||
if mustHaveDestination {
|
||||
if req.Request.Destination != req.IDP.SSOURL.String() {
|
||||
return fmt.Errorf("expected destination to be %q, not %q", req.IDP.SSOURL.String(), req.Request.Destination)
|
||||
}
|
||||
}
|
||||
|
||||
if req.Request.IssueInstant.Add(MaxIssueDelay).Before(req.Now) {
|
||||
return fmt.Errorf("request expired at %s",
|
||||
req.Request.IssueInstant.Add(MaxIssueDelay))
|
||||
}
|
||||
if req.Request.Version != "2.0" {
|
||||
return fmt.Errorf("expected SAML request version 2.0 got %v", req.Request.Version)
|
||||
}
|
||||
|
||||
// find the service provider
|
||||
serviceProviderID := req.Request.Issuer.Value
|
||||
serviceProvider, err := req.IDP.ServiceProviderProvider.GetServiceProvider(req.HTTPRequest, serviceProviderID)
|
||||
if err == os.ErrNotExist {
|
||||
return fmt.Errorf("cannot handle request from unknown service provider %s", serviceProviderID)
|
||||
} else if err != nil {
|
||||
return fmt.Errorf("cannot find service provider %s: %v", serviceProviderID, err)
|
||||
}
|
||||
req.ServiceProviderMetadata = serviceProvider
|
||||
|
||||
// Check that the ACS URL matches an ACS endpoint in the SP metadata.
|
||||
if err := req.getACSEndpoint(); err != nil {
|
||||
return fmt.Errorf("cannot find assertion consumer service: %v", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (req *IdpAuthnRequest) getACSEndpoint() error {
|
||||
if req.Request.AssertionConsumerServiceIndex != "" {
|
||||
for _, spssoDescriptor := range req.ServiceProviderMetadata.SPSSODescriptors {
|
||||
for _, spAssertionConsumerService := range spssoDescriptor.AssertionConsumerServices {
|
||||
if strconv.Itoa(spAssertionConsumerService.Index) == req.Request.AssertionConsumerServiceIndex {
|
||||
req.SPSSODescriptor = &spssoDescriptor
|
||||
req.ACSEndpoint = &spAssertionConsumerService
|
||||
return nil
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if req.Request.AssertionConsumerServiceURL != "" {
|
||||
for _, spssoDescriptor := range req.ServiceProviderMetadata.SPSSODescriptors {
|
||||
for _, spAssertionConsumerService := range spssoDescriptor.AssertionConsumerServices {
|
||||
if spAssertionConsumerService.Location == req.Request.AssertionConsumerServiceURL {
|
||||
req.SPSSODescriptor = &spssoDescriptor
|
||||
req.ACSEndpoint = &spAssertionConsumerService
|
||||
return nil
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Some service providers, like the Microsoft Azure AD service provider, issue
|
||||
// assertion requests that don't specify an ACS url at all.
|
||||
if req.Request.AssertionConsumerServiceURL == "" && req.Request.AssertionConsumerServiceIndex == "" {
|
||||
// find a default ACS binding in the metadata that we can use
|
||||
for _, spssoDescriptor := range req.ServiceProviderMetadata.SPSSODescriptors {
|
||||
for _, spAssertionConsumerService := range spssoDescriptor.AssertionConsumerServices {
|
||||
if spAssertionConsumerService.IsDefault != nil && *spAssertionConsumerService.IsDefault {
|
||||
switch spAssertionConsumerService.Binding {
|
||||
case HTTPPostBinding, HTTPRedirectBinding:
|
||||
req.SPSSODescriptor = &spssoDescriptor
|
||||
req.ACSEndpoint = &spAssertionConsumerService
|
||||
return nil
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// if we can't find a default, use *any* ACS binding
|
||||
for _, spssoDescriptor := range req.ServiceProviderMetadata.SPSSODescriptors {
|
||||
for _, spAssertionConsumerService := range spssoDescriptor.AssertionConsumerServices {
|
||||
switch spAssertionConsumerService.Binding {
|
||||
case HTTPPostBinding, HTTPRedirectBinding:
|
||||
req.SPSSODescriptor = &spssoDescriptor
|
||||
req.ACSEndpoint = &spAssertionConsumerService
|
||||
return nil
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return os.ErrNotExist // no ACS url found or specified
|
||||
}
|
||||
|
||||
// DefaultAssertionMaker produces a SAML assertion for the
|
||||
// given request and assigns it to req.Assertion.
|
||||
type DefaultAssertionMaker struct {
|
||||
}
|
||||
|
||||
// MakeAssertion implements AssertionMaker. It produces a SAML assertion from the
|
||||
// given request and assigns it to req.Assertion.
|
||||
func (DefaultAssertionMaker) MakeAssertion(req *IdpAuthnRequest, session *Session) error {
|
||||
attributes := []Attribute{}
|
||||
|
||||
var attributeConsumingService *AttributeConsumingService
|
||||
for _, acs := range req.SPSSODescriptor.AttributeConsumingServices {
|
||||
if acs.IsDefault != nil && *acs.IsDefault {
|
||||
attributeConsumingService = &acs
|
||||
break
|
||||
}
|
||||
}
|
||||
if attributeConsumingService == nil {
|
||||
for _, acs := range req.SPSSODescriptor.AttributeConsumingServices {
|
||||
attributeConsumingService = &acs
|
||||
break
|
||||
}
|
||||
}
|
||||
if attributeConsumingService == nil {
|
||||
attributeConsumingService = &AttributeConsumingService{}
|
||||
}
|
||||
|
||||
for _, requestedAttribute := range attributeConsumingService.RequestedAttributes {
|
||||
if requestedAttribute.NameFormat == "urn:oasis:names:tc:SAML:2.0:attrname-format:basic" || requestedAttribute.NameFormat == "urn:oasis:names:tc:SAML:2.0:attrname-format:unspecified" {
|
||||
attrName := requestedAttribute.Name
|
||||
attrName = regexp.MustCompile("[^A-Za-z0-9]+").ReplaceAllString(attrName, "")
|
||||
switch attrName {
|
||||
case "email", "emailaddress":
|
||||
attributes = append(attributes, Attribute{
|
||||
FriendlyName: requestedAttribute.FriendlyName,
|
||||
Name: requestedAttribute.Name,
|
||||
NameFormat: requestedAttribute.NameFormat,
|
||||
Values: []AttributeValue{{
|
||||
Type: "xs:string",
|
||||
Value: session.UserEmail,
|
||||
}},
|
||||
})
|
||||
case "name", "fullname", "cn", "commonname":
|
||||
attributes = append(attributes, Attribute{
|
||||
FriendlyName: requestedAttribute.FriendlyName,
|
||||
Name: requestedAttribute.Name,
|
||||
NameFormat: requestedAttribute.NameFormat,
|
||||
Values: []AttributeValue{{
|
||||
Type: "xs:string",
|
||||
Value: session.UserCommonName,
|
||||
}},
|
||||
})
|
||||
case "givenname", "firstname":
|
||||
attributes = append(attributes, Attribute{
|
||||
FriendlyName: requestedAttribute.FriendlyName,
|
||||
Name: requestedAttribute.Name,
|
||||
NameFormat: requestedAttribute.NameFormat,
|
||||
Values: []AttributeValue{{
|
||||
Type: "xs:string",
|
||||
Value: session.UserGivenName,
|
||||
}},
|
||||
})
|
||||
case "surname", "lastname", "familyname":
|
||||
attributes = append(attributes, Attribute{
|
||||
FriendlyName: requestedAttribute.FriendlyName,
|
||||
Name: requestedAttribute.Name,
|
||||
NameFormat: requestedAttribute.NameFormat,
|
||||
Values: []AttributeValue{{
|
||||
Type: "xs:string",
|
||||
Value: session.UserSurname,
|
||||
}},
|
||||
})
|
||||
case "uid", "user", "userid":
|
||||
attributes = append(attributes, Attribute{
|
||||
FriendlyName: requestedAttribute.FriendlyName,
|
||||
Name: requestedAttribute.Name,
|
||||
NameFormat: requestedAttribute.NameFormat,
|
||||
Values: []AttributeValue{{
|
||||
Type: "xs:string",
|
||||
Value: session.UserName,
|
||||
}},
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if session.UserName != "" {
|
||||
attributes = append(attributes, Attribute{
|
||||
FriendlyName: "uid",
|
||||
Name: "urn:oid:0.9.2342.19200300.100.1.1",
|
||||
NameFormat: "urn:oasis:names:tc:SAML:2.0:attrname-format:uri",
|
||||
Values: []AttributeValue{{
|
||||
Type: "xs:string",
|
||||
Value: session.UserName,
|
||||
}},
|
||||
})
|
||||
}
|
||||
|
||||
if session.UserEmail != "" {
|
||||
attributes = append(attributes, Attribute{
|
||||
FriendlyName: "eduPersonPrincipalName",
|
||||
Name: "urn:oid:1.3.6.1.4.1.5923.1.1.1.6",
|
||||
NameFormat: "urn:oasis:names:tc:SAML:2.0:attrname-format:uri",
|
||||
Values: []AttributeValue{{
|
||||
Type: "xs:string",
|
||||
Value: session.UserEmail,
|
||||
}},
|
||||
})
|
||||
}
|
||||
if session.UserSurname != "" {
|
||||
attributes = append(attributes, Attribute{
|
||||
FriendlyName: "sn",
|
||||
Name: "urn:oid:2.5.4.4",
|
||||
NameFormat: "urn:oasis:names:tc:SAML:2.0:attrname-format:uri",
|
||||
Values: []AttributeValue{{
|
||||
Type: "xs:string",
|
||||
Value: session.UserSurname,
|
||||
}},
|
||||
})
|
||||
}
|
||||
if session.UserGivenName != "" {
|
||||
attributes = append(attributes, Attribute{
|
||||
FriendlyName: "givenName",
|
||||
Name: "urn:oid:2.5.4.42",
|
||||
NameFormat: "urn:oasis:names:tc:SAML:2.0:attrname-format:uri",
|
||||
Values: []AttributeValue{{
|
||||
Type: "xs:string",
|
||||
Value: session.UserGivenName,
|
||||
}},
|
||||
})
|
||||
}
|
||||
|
||||
if session.UserCommonName != "" {
|
||||
attributes = append(attributes, Attribute{
|
||||
FriendlyName: "cn",
|
||||
Name: "urn:oid:2.5.4.3",
|
||||
NameFormat: "urn:oasis:names:tc:SAML:2.0:attrname-format:uri",
|
||||
Values: []AttributeValue{{
|
||||
Type: "xs:string",
|
||||
Value: session.UserCommonName,
|
||||
}},
|
||||
})
|
||||
}
|
||||
|
||||
if len(session.Groups) != 0 {
|
||||
groupMemberAttributeValues := []AttributeValue{}
|
||||
for _, group := range session.Groups {
|
||||
groupMemberAttributeValues = append(groupMemberAttributeValues, AttributeValue{
|
||||
Type: "xs:string",
|
||||
Value: group,
|
||||
})
|
||||
}
|
||||
attributes = append(attributes, Attribute{
|
||||
FriendlyName: "eduPersonAffiliation",
|
||||
Name: "urn:oid:1.3.6.1.4.1.5923.1.1.1.1",
|
||||
NameFormat: "urn:oasis:names:tc:SAML:2.0:attrname-format:uri",
|
||||
Values: groupMemberAttributeValues,
|
||||
})
|
||||
}
|
||||
|
||||
// allow for some clock skew in the validity period using the
|
||||
// issuer's apparent clock.
|
||||
notBefore := req.Now.Add(-1 * MaxClockSkew)
|
||||
notOnOrAfterAfter := req.Now.Add(MaxIssueDelay)
|
||||
if notBefore.Before(req.Request.IssueInstant) {
|
||||
notBefore = req.Request.IssueInstant
|
||||
notOnOrAfterAfter = notBefore.Add(MaxIssueDelay)
|
||||
}
|
||||
|
||||
req.Assertion = &Assertion{
|
||||
ID: fmt.Sprintf("id-%x", randomBytes(20)),
|
||||
IssueInstant: TimeNow(),
|
||||
Version: "2.0",
|
||||
Issuer: Issuer{
|
||||
Format: "urn:oasis:names:tc:SAML:2.0:nameid-format:entity",
|
||||
Value: req.IDP.Metadata().EntityID,
|
||||
},
|
||||
Subject: &Subject{
|
||||
NameID: &NameID{
|
||||
Format: "urn:oasis:names:tc:SAML:2.0:nameid-format:transient",
|
||||
NameQualifier: req.IDP.Metadata().EntityID,
|
||||
SPNameQualifier: req.ServiceProviderMetadata.EntityID,
|
||||
Value: session.NameID,
|
||||
},
|
||||
SubjectConfirmations: []SubjectConfirmation{
|
||||
SubjectConfirmation{
|
||||
Method: "urn:oasis:names:tc:SAML:2.0:cm:bearer",
|
||||
SubjectConfirmationData: &SubjectConfirmationData{
|
||||
Address: req.HTTPRequest.RemoteAddr,
|
||||
InResponseTo: req.Request.ID,
|
||||
NotOnOrAfter: req.Now.Add(MaxIssueDelay),
|
||||
Recipient: req.ACSEndpoint.Location,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
Conditions: &Conditions{
|
||||
NotBefore: notBefore,
|
||||
NotOnOrAfter: notOnOrAfterAfter,
|
||||
AudienceRestrictions: []AudienceRestriction{
|
||||
AudienceRestriction{
|
||||
Audience: Audience{Value: req.ServiceProviderMetadata.EntityID},
|
||||
},
|
||||
},
|
||||
},
|
||||
AuthnStatements: []AuthnStatement{
|
||||
AuthnStatement{
|
||||
AuthnInstant: session.CreateTime,
|
||||
SessionIndex: session.Index,
|
||||
SubjectLocality: &SubjectLocality{
|
||||
Address: req.HTTPRequest.RemoteAddr,
|
||||
},
|
||||
AuthnContext: AuthnContext{
|
||||
AuthnContextClassRef: &AuthnContextClassRef{
|
||||
Value: "urn:oasis:names:tc:SAML:2.0:ac:classes:PasswordProtectedTransport",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
AttributeStatements: []AttributeStatement{
|
||||
AttributeStatement{
|
||||
Attributes: attributes,
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// The Canonicalizer prefix list MUST be empty. Various implementations
|
||||
// (maybe ours?) do not appear to support non-empty prefix lists in XML C14N.
|
||||
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 {
|
||||
return err
|
||||
}
|
||||
|
||||
assertionEl := req.Assertion.Element()
|
||||
|
||||
signedAssertionEl, err := signingContext.SignEnveloped(assertionEl)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
sigEl := signedAssertionEl.Child[len(signedAssertionEl.Child)-1]
|
||||
req.Assertion.Signature = sigEl.(*etree.Element)
|
||||
signedAssertionEl = req.Assertion.Element()
|
||||
|
||||
certBuf, err := req.getSPEncryptionCert()
|
||||
if err == os.ErrNotExist {
|
||||
req.AssertionEl = signedAssertionEl
|
||||
return nil
|
||||
} else if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
var signedAssertionBuf []byte
|
||||
{
|
||||
doc := etree.NewDocument()
|
||||
doc.SetRoot(signedAssertionEl)
|
||||
signedAssertionBuf, err = doc.WriteToBytes()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
encryptor := xmlenc.OAEP()
|
||||
encryptor.BlockCipher = xmlenc.AES128CBC
|
||||
encryptor.DigestMethod = &xmlenc.SHA1
|
||||
encryptedDataEl, err := encryptor.Encrypt(certBuf, signedAssertionBuf)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
encryptedDataEl.CreateAttr("Type", "http://www.w3.org/2001/04/xmlenc#Element")
|
||||
|
||||
encryptedAssertionEl := etree.NewElement("saml:EncryptedAssertion")
|
||||
encryptedAssertionEl.AddChild(encryptedDataEl)
|
||||
req.AssertionEl = encryptedAssertionEl
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// WriteResponse writes the `Response` to the http.ResponseWriter. If
|
||||
// `Response` is not already set, it calls MakeResponse to produce it.
|
||||
func (req *IdpAuthnRequest) WriteResponse(w http.ResponseWriter) error {
|
||||
if req.ResponseEl == nil {
|
||||
if err := req.MakeResponse(); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
doc := etree.NewDocument()
|
||||
doc.SetRoot(req.ResponseEl)
|
||||
responseBuf, err := doc.WriteToBytes()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// the only supported binding is the HTTP-POST binding
|
||||
switch req.ACSEndpoint.Binding {
|
||||
case HTTPPostBinding:
|
||||
tmpl := template.Must(template.New("saml-post-form").Parse(`<html>` +
|
||||
`<form method="post" action="{{.URL}}" id="SAMLResponseForm">` +
|
||||
`<input type="hidden" name="SAMLResponse" value="{{.SAMLResponse}}" />` +
|
||||
`<input type="hidden" name="RelayState" value="{{.RelayState}}" />` +
|
||||
`<input id="SAMLSubmitButton" type="submit" value="Continue" />` +
|
||||
`</form>` +
|
||||
`<script>document.getElementById('SAMLSubmitButton').style.visibility='hidden';</script>` +
|
||||
`<script>document.getElementById('SAMLResponseForm').submit();</script>` +
|
||||
`</html>`))
|
||||
data := struct {
|
||||
URL string
|
||||
SAMLResponse string
|
||||
RelayState string
|
||||
}{
|
||||
URL: req.ACSEndpoint.Location,
|
||||
SAMLResponse: base64.StdEncoding.EncodeToString(responseBuf),
|
||||
RelayState: req.RelayState,
|
||||
}
|
||||
|
||||
buf := bytes.NewBuffer(nil)
|
||||
if err := tmpl.Execute(buf, data); err != nil {
|
||||
return err
|
||||
}
|
||||
if _, err := io.Copy(w, buf); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
|
||||
default:
|
||||
return fmt.Errorf("%s: unsupported binding %s",
|
||||
req.ServiceProviderMetadata.EntityID,
|
||||
req.ACSEndpoint.Binding)
|
||||
}
|
||||
}
|
||||
|
||||
// getSPEncryptionCert returns the certificate which we can use to encrypt things
|
||||
// to the SP in PEM format, or nil if no such certificate is found.
|
||||
func (req *IdpAuthnRequest) getSPEncryptionCert() (*x509.Certificate, error) {
|
||||
certStr := ""
|
||||
for _, keyDescriptor := range req.SPSSODescriptor.KeyDescriptors {
|
||||
if keyDescriptor.Use == "encryption" {
|
||||
certStr = keyDescriptor.KeyInfo.Certificate
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
// If there are no certs explicitly labeled for encryption, return the first
|
||||
// non-empty cert we find.
|
||||
if certStr == "" {
|
||||
for _, keyDescriptor := range req.SPSSODescriptor.KeyDescriptors {
|
||||
if keyDescriptor.Use == "" && keyDescriptor.KeyInfo.Certificate != "" {
|
||||
certStr = keyDescriptor.KeyInfo.Certificate
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if certStr == "" {
|
||||
return nil, os.ErrNotExist
|
||||
}
|
||||
|
||||
// cleanup whitespace and re-encode a PEM
|
||||
certStr = regexp.MustCompile(`\s+`).ReplaceAllString(certStr, "")
|
||||
certBytes, err := base64.StdEncoding.DecodeString(certStr)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("cannot decode certificate base64: %v", err)
|
||||
}
|
||||
cert, err := x509.ParseCertificate(certBytes)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("cannot parse certificate: %v", err)
|
||||
}
|
||||
return cert, nil
|
||||
}
|
||||
|
||||
// unmarshalEtreeHack parses `el` and sets values in the structure `v`.
|
||||
//
|
||||
// This is a hack -- it first serializes the element, then uses xml.Unmarshal.
|
||||
func unmarshalEtreeHack(el *etree.Element, v interface{}) error {
|
||||
doc := etree.NewDocument()
|
||||
doc.SetRoot(el)
|
||||
buf, err := doc.WriteToBytes()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return xml.Unmarshal(buf, v)
|
||||
}
|
||||
|
||||
// MakeResponse creates and assigns a new SAML response in ResponseEl. `Assertion` must
|
||||
// be non-nil. If MakeAssertionEl() has not been called, this function calls it for
|
||||
// you.
|
||||
func (req *IdpAuthnRequest) MakeResponse() error {
|
||||
if req.AssertionEl == nil {
|
||||
if err := req.MakeAssertionEl(); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
response := &Response{
|
||||
Destination: req.ACSEndpoint.Location,
|
||||
ID: fmt.Sprintf("id-%x", randomBytes(20)),
|
||||
InResponseTo: req.Request.ID,
|
||||
IssueInstant: req.Now,
|
||||
Version: "2.0",
|
||||
Issuer: &Issuer{
|
||||
Format: "urn:oasis:names:tc:SAML:2.0:nameid-format:entity",
|
||||
Value: req.IDP.MetadataURL.String(),
|
||||
},
|
||||
Status: Status{
|
||||
StatusCode: StatusCode{
|
||||
Value: StatusSuccess,
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
responseEl := response.Element()
|
||||
responseEl.AddChild(req.AssertionEl) // AssertionEl either an EncryptedAssertion or Assertion element
|
||||
|
||||
// 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 {
|
||||
return err
|
||||
}
|
||||
|
||||
signedResponseEl, err := signingContext.SignEnveloped(responseEl)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
sigEl := signedResponseEl.ChildElements()[len(signedResponseEl.ChildElements())-1]
|
||||
response.Signature = sigEl
|
||||
responseEl = response.Element()
|
||||
responseEl.AddChild(req.AssertionEl)
|
||||
}
|
||||
|
||||
req.ResponseEl = responseEl
|
||||
return nil
|
||||
}
|
31
vendor/github.com/crewjam/saml/logger/logger.go
generated
vendored
Normal file
31
vendor/github.com/crewjam/saml/logger/logger.go
generated
vendored
Normal file
@ -0,0 +1,31 @@
|
||||
package logger
|
||||
|
||||
import (
|
||||
"log"
|
||||
"os"
|
||||
)
|
||||
|
||||
// Interface provides the minimal logging interface
|
||||
type Interface interface {
|
||||
// Printf prints to the logger using the format.
|
||||
Printf(format string, v ...interface{})
|
||||
// Print prints to the logger.
|
||||
Print(v ...interface{})
|
||||
// Println prints new line.
|
||||
Println(v ...interface{})
|
||||
// Fatal is equivalent to Print() followed by a call to os.Exit(1).
|
||||
Fatal(v ...interface{})
|
||||
// Fatalf is equivalent to Printf() followed by a call to os.Exit(1).
|
||||
Fatalf(format string, v ...interface{})
|
||||
// Fatalln is equivalent to Println() followed by a call to os.Exit(1).
|
||||
Fatalln(v ...interface{})
|
||||
// Panic is equivalent to Print() followed by a call to panic().
|
||||
Panic(v ...interface{})
|
||||
// Panicf is equivalent to Printf() followed by a call to panic().
|
||||
Panicf(format string, v ...interface{})
|
||||
// Panicln is equivalent to Println() followed by a call to panic().
|
||||
Panicln(v ...interface{})
|
||||
}
|
||||
|
||||
// DefaultLogger logs messages to os.Stdout
|
||||
var DefaultLogger = log.New(os.Stdout, "", log.LstdFlags)
|
286
vendor/github.com/crewjam/saml/metadata.go
generated
vendored
Normal file
286
vendor/github.com/crewjam/saml/metadata.go
generated
vendored
Normal file
@ -0,0 +1,286 @@
|
||||
package saml
|
||||
|
||||
import (
|
||||
"encoding/xml"
|
||||
"time"
|
||||
|
||||
"github.com/beevik/etree"
|
||||
)
|
||||
|
||||
// HTTPPostBinding is the official URN for the HTTP-POST binding (transport)
|
||||
var HTTPPostBinding = "urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST"
|
||||
|
||||
// HTTPRedirectBinding is the official URN for the HTTP-Redirect binding (transport)
|
||||
var HTTPRedirectBinding = "urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect"
|
||||
|
||||
// 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
|
||||
type EntitiesDescriptor struct {
|
||||
XMLName xml.Name `xml:"urn:oasis:names:tc:SAML:2.0:metadata EntitiesDescriptor"`
|
||||
ID *string `xml:",attr,omitempty"`
|
||||
ValidUntil *time.Time `xml:"validUntil,attr,omitempty"`
|
||||
CacheDuration *time.Duration `xml:"cacheDuration,attr,omitempty"`
|
||||
Name *string `xml:",attr,omitempty"`
|
||||
Signature *etree.Element
|
||||
EntitiesDescriptors []EntitiesDescriptor `xml:"urn:oasis:names:tc:SAML:2.0:metadata EntitiesDescriptor"`
|
||||
EntityDescriptors []EntityDescriptor `xml:"urn:oasis:names:tc:SAML:2.0:metadata EntityDescriptor"`
|
||||
}
|
||||
|
||||
// Metadata as been renamed to EntityDescriptor
|
||||
//
|
||||
// This change was made to be consistent with the rest of the API which uses names
|
||||
// from the SAML specification for types.
|
||||
//
|
||||
// This is a tombstone to help you discover this fact. You should update references
|
||||
// to saml.Metadata to be saml.EntityDescriptor.
|
||||
var Metadata = struct{}{}
|
||||
|
||||
// EntityDescriptor represents the SAML EntityDescriptor object.
|
||||
//
|
||||
// See http://docs.oasis-open.org/security/saml/v2.0/saml-metadata-2.0-os.pdf §2.3.2
|
||||
type EntityDescriptor struct {
|
||||
XMLName xml.Name `xml:"urn:oasis:names:tc:SAML:2.0:metadata EntityDescriptor"`
|
||||
EntityID string `xml:"entityID,attr"`
|
||||
ID string `xml:",attr,omitempty"`
|
||||
ValidUntil time.Time `xml:"validUntil,attr,omitempty"`
|
||||
CacheDuration time.Duration `xml:"cacheDuration,attr,omitempty"`
|
||||
Signature *etree.Element
|
||||
RoleDescriptors []RoleDescriptor `xml:"RoleDescriptor"`
|
||||
IDPSSODescriptors []IDPSSODescriptor `xml:"IDPSSODescriptor"`
|
||||
SPSSODescriptors []SPSSODescriptor `xml:"SPSSODescriptor"`
|
||||
AuthnAuthorityDescriptors []AuthnAuthorityDescriptor `xml:"AuthnAuthorityDescriptor"`
|
||||
AttributeAuthorityDescriptors []AttributeAuthorityDescriptor `xml:"AttributeAuthorityDescriptor"`
|
||||
PDPDescriptors []PDPDescriptor `xml:"PDPDescriptor"`
|
||||
AffiliationDescriptor *AffiliationDescriptor
|
||||
Organization *Organization
|
||||
ContactPerson *ContactPerson
|
||||
AdditionalMetadataLocations []string `xml:"AdditionalMetadataLocation"`
|
||||
}
|
||||
|
||||
// MarshalXML implements xml.Marshaler
|
||||
func (m EntityDescriptor) MarshalXML(e *xml.Encoder, start xml.StartElement) error {
|
||||
type Alias EntityDescriptor
|
||||
aux := &struct {
|
||||
ValidUntil RelaxedTime `xml:"validUntil,attr,omitempty"`
|
||||
CacheDuration Duration `xml:"cacheDuration,attr,omitempty"`
|
||||
*Alias
|
||||
}{
|
||||
ValidUntil: RelaxedTime(m.ValidUntil),
|
||||
CacheDuration: Duration(m.CacheDuration),
|
||||
Alias: (*Alias)(&m),
|
||||
}
|
||||
return e.Encode(aux)
|
||||
}
|
||||
|
||||
// UnmarshalXML implements xml.Unmarshaler
|
||||
func (m *EntityDescriptor) UnmarshalXML(d *xml.Decoder, start xml.StartElement) error {
|
||||
type Alias EntityDescriptor
|
||||
aux := &struct {
|
||||
ValidUntil RelaxedTime `xml:"validUntil,attr,omitempty"`
|
||||
CacheDuration Duration `xml:"cacheDuration,attr,omitempty"`
|
||||
*Alias
|
||||
}{
|
||||
Alias: (*Alias)(m),
|
||||
}
|
||||
if err := d.DecodeElement(aux, &start); err != nil {
|
||||
return err
|
||||
}
|
||||
m.ValidUntil = time.Time(aux.ValidUntil)
|
||||
m.CacheDuration = time.Duration(aux.CacheDuration)
|
||||
return nil
|
||||
}
|
||||
|
||||
// Organization represents the SAML Organization object.
|
||||
//
|
||||
// See http://docs.oasis-open.org/security/saml/v2.0/saml-metadata-2.0-os.pdf §2.3.2.1
|
||||
type Organization struct {
|
||||
OrganizationNames []LocalizedName `xml:"OrganizationName"`
|
||||
OrganizationDisplayNames []LocalizedName `xml:"OrganizationDisplayName"`
|
||||
OrganizationURLs []LocalizedURI `xml:"OrganizationURL"`
|
||||
}
|
||||
|
||||
// LocalizedName represents the SAML type localizedNameType.
|
||||
//
|
||||
// See http://docs.oasis-open.org/security/saml/v2.0/saml-metadata-2.0-os.pdf §2.2.4
|
||||
type LocalizedName struct {
|
||||
Lang string `xml:"xml lang,attr"`
|
||||
Value string `xml:",chardata"`
|
||||
}
|
||||
|
||||
// LocalizedURI represents the SAML type localizedURIType.
|
||||
//
|
||||
// See http://docs.oasis-open.org/security/saml/v2.0/saml-metadata-2.0-os.pdf §2.2.5
|
||||
type LocalizedURI struct {
|
||||
Lang string `xml:"xml lang,attr"`
|
||||
Value string `xml:",chardata"`
|
||||
}
|
||||
|
||||
// ContactPerson represents the SAML element ContactPerson.
|
||||
//
|
||||
// See http://docs.oasis-open.org/security/saml/v2.0/saml-metadata-2.0-os.pdf §2.3.2.2
|
||||
type ContactPerson struct {
|
||||
ContactType string `xml:"contactType,attr"`
|
||||
Company string
|
||||
GivenName string
|
||||
SurName string
|
||||
EmailAddresses []string `xml:"EmailAddress"`
|
||||
TelephoneNumbers []string `xml:"TelephoneNumber"`
|
||||
}
|
||||
|
||||
// RoleDescriptor represents the SAML element RoleDescriptor.
|
||||
//
|
||||
// See http://docs.oasis-open.org/security/saml/v2.0/saml-metadata-2.0-os.pdf §2.4.1
|
||||
type RoleDescriptor struct {
|
||||
ID string `xml:",attr,omitempty"`
|
||||
ValidUntil time.Time `xml:"validUntil,attr,omitempty"`
|
||||
CacheDuration time.Duration `xml:"cacheDuration,attr,omitempty"`
|
||||
ProtocolSupportEnumeration string `xml:"protocolSupportEnumeration,attr"`
|
||||
ErrorURL string `xml:"errorURL,attr,omitempty"`
|
||||
Signature *etree.Element
|
||||
KeyDescriptors []KeyDescriptor `xml:"KeyDescriptor,omitempty"`
|
||||
Organization *Organization `xml:"Organization,omitempty"`
|
||||
ContactPeople []ContactPerson `xml:"ContactPerson,omitempty"`
|
||||
}
|
||||
|
||||
// KeyDescriptor represents the XMLSEC object of the same name
|
||||
type KeyDescriptor struct {
|
||||
Use string `xml:"use,attr"`
|
||||
KeyInfo KeyInfo `xml:"http://www.w3.org/2000/09/xmldsig# KeyInfo"`
|
||||
EncryptionMethods []EncryptionMethod `xml:"EncryptionMethod"`
|
||||
}
|
||||
|
||||
// EncryptionMethod represents the XMLSEC object of the same name
|
||||
type EncryptionMethod struct {
|
||||
Algorithm string `xml:"Algorithm,attr"`
|
||||
}
|
||||
|
||||
// KeyInfo represents the XMLSEC object of the same name
|
||||
//
|
||||
// TODO(ross): revisit xmldsig and make this type more complete
|
||||
type KeyInfo struct {
|
||||
XMLName xml.Name `xml:"http://www.w3.org/2000/09/xmldsig# KeyInfo"`
|
||||
Certificate string `xml:"X509Data>X509Certificate"`
|
||||
}
|
||||
|
||||
// Endpoint represents the SAML EndpointType object.
|
||||
//
|
||||
// See http://docs.oasis-open.org/security/saml/v2.0/saml-metadata-2.0-os.pdf §2.2.2
|
||||
type Endpoint struct {
|
||||
Binding string `xml:"Binding,attr"`
|
||||
Location string `xml:"Location,attr"`
|
||||
ResponseLocation string `xml:"ResponseLocation,attr,omitempty"`
|
||||
}
|
||||
|
||||
// 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
|
||||
type IndexedEndpoint struct {
|
||||
Binding string `xml:"Binding,attr"`
|
||||
Location string `xml:"Location,attr"`
|
||||
ResponseLocation *string `xml:"ResponseLocation,attr,omitempty"`
|
||||
Index int `xml:"index,attr"`
|
||||
IsDefault *bool `xml:"isDefault,attr"`
|
||||
}
|
||||
|
||||
// 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
|
||||
type SSODescriptor struct {
|
||||
RoleDescriptor
|
||||
ArtifactResolutionServices []IndexedEndpoint `xml:"ArtifactResolutionService"`
|
||||
SingleLogoutServices []Endpoint `xml:"SingleLogoutService"`
|
||||
ManageNameIDServices []Endpoint `xml:"ManageNameIDService"`
|
||||
NameIDFormats []NameIDFormat `xml:"NameIDFormat"`
|
||||
}
|
||||
|
||||
// IDPSSODescriptor represents the SAML IDPSSODescriptorType object.
|
||||
//
|
||||
// See http://docs.oasis-open.org/security/saml/v2.0/saml-metadata-2.0-os.pdf §2.4.3
|
||||
type IDPSSODescriptor struct {
|
||||
XMLName xml.Name `xml:"urn:oasis:names:tc:SAML:2.0:metadata IDPSSODescriptor"`
|
||||
SSODescriptor
|
||||
WantAuthnRequestsSigned *bool `xml:",attr"`
|
||||
|
||||
SingleSignOnServices []Endpoint `xml:"SingleSignOnService"`
|
||||
NameIDMappingServices []Endpoint `xml:"NameIDMappingService"`
|
||||
AssertionIDRequestServices []Endpoint `xml:"AssertionIDRequestService"`
|
||||
AttributeProfiles []string `xml:"AttributeProfile"`
|
||||
Attributes []Attribute `xml:"Attribute"`
|
||||
}
|
||||
|
||||
// SPSSODescriptor represents the SAML SPSSODescriptorType object.
|
||||
//
|
||||
// See http://docs.oasis-open.org/security/saml/v2.0/saml-metadata-2.0-os.pdf §2.4.2
|
||||
type SPSSODescriptor struct {
|
||||
XMLName xml.Name `xml:"urn:oasis:names:tc:SAML:2.0:metadata SPSSODescriptor"`
|
||||
SSODescriptor
|
||||
AuthnRequestsSigned *bool `xml:",attr"`
|
||||
WantAssertionsSigned *bool `xml:",attr"`
|
||||
AssertionConsumerServices []IndexedEndpoint `xml:"AssertionConsumerService"`
|
||||
AttributeConsumingServices []AttributeConsumingService `xml:"AttributeConsumingService"`
|
||||
}
|
||||
|
||||
// AttributeConsumingService represents the SAML AttributeConsumingService object.
|
||||
//
|
||||
// See http://docs.oasis-open.org/security/saml/v2.0/saml-metadata-2.0-os.pdf §2.4.4.1
|
||||
type AttributeConsumingService struct {
|
||||
Index int `xml:"index,attr"`
|
||||
IsDefault *bool `xml:"isDefault,attr"`
|
||||
ServiceNames []LocalizedName `xml:"ServiceName"`
|
||||
ServiceDescriptions []LocalizedName `xml:"ServiceDescription"`
|
||||
RequestedAttributes []RequestedAttribute `xml:"RequestedAttribute"`
|
||||
}
|
||||
|
||||
// RequestedAttribute represents the SAML RequestedAttribute object.
|
||||
//
|
||||
// See http://docs.oasis-open.org/security/saml/v2.0/saml-metadata-2.0-os.pdf §2.4.4.2
|
||||
type RequestedAttribute struct {
|
||||
Attribute
|
||||
IsRequired *bool `xml:"isRequired,attr"`
|
||||
}
|
||||
|
||||
// AuthnAuthorityDescriptor represents the SAML AuthnAuthorityDescriptor object.
|
||||
//
|
||||
// See http://docs.oasis-open.org/security/saml/v2.0/saml-metadata-2.0-os.pdf §2.4.5
|
||||
type AuthnAuthorityDescriptor struct {
|
||||
RoleDescriptor
|
||||
AuthnQueryServices []Endpoint `xml:"AuthnQueryService"`
|
||||
AssertionIDRequestServices []Endpoint `xml:"AssertionIDRequestService"`
|
||||
NameIDFormats []NameIDFormat `xml:"NameIDFormat"`
|
||||
}
|
||||
|
||||
// PDPDescriptor represents the SAML PDPDescriptor object.
|
||||
//
|
||||
// See http://docs.oasis-open.org/security/saml/v2.0/saml-metadata-2.0-os.pdf §2.4.6
|
||||
type PDPDescriptor struct {
|
||||
RoleDescriptor
|
||||
AuthzServices []Endpoint `xml:"AuthzService"`
|
||||
AssertionIDRequestServices []Endpoint `xml:"AssertionIDRequestService"`
|
||||
NameIDFormats []NameIDFormat `xml:"NameIDFormat"`
|
||||
}
|
||||
|
||||
// AttributeAuthorityDescriptor represents the SAML AttributeAuthorityDescriptor object.
|
||||
//
|
||||
// See http://docs.oasis-open.org/security/saml/v2.0/saml-metadata-2.0-os.pdf §2.4.7
|
||||
type AttributeAuthorityDescriptor struct {
|
||||
RoleDescriptor
|
||||
AttributeServices []Endpoint `xml:"AttributeService"`
|
||||
AssertionIDRequestServices []Endpoint `xml:"AssertionIDRequestService"`
|
||||
NameIDFormats []NameIDFormat `xml:"NameIDFormat"`
|
||||
AttributeProfiles []string `xml:"AttributeProfile"`
|
||||
Attributes []Attribute `xml:"Attribute"`
|
||||
}
|
||||
|
||||
// AffiliationDescriptor represents the SAML AffiliationDescriptor object.
|
||||
//
|
||||
// See http://docs.oasis-open.org/security/saml/v2.0/saml-metadata-2.0-os.pdf §2.5
|
||||
type AffiliationDescriptor struct {
|
||||
AffiliationOwnerID string `xml:"affiliationOwnerID,attr"`
|
||||
ID string `xml:",attr"`
|
||||
ValidUntil time.Time `xml:"validUntil,attr,omitempty"`
|
||||
CacheDuration time.Duration `xml:"cacheDuration,attr"`
|
||||
Signature *etree.Element
|
||||
AffiliateMembers []string `xml:"AffiliateMember"`
|
||||
KeyDescriptors []KeyDescriptor `xml:"KeyDescriptor"`
|
||||
}
|
164
vendor/github.com/crewjam/saml/saml.go
generated
vendored
Normal file
164
vendor/github.com/crewjam/saml/saml.go
generated
vendored
Normal file
@ -0,0 +1,164 @@
|
||||
//
|
||||
// Package saml contains a partial implementation of the SAML standard in golang.
|
||||
// SAML is a standard for identity federation, i.e. either allowing a third party to authenticate your users or allowing third parties to rely on us to authenticate their users.
|
||||
//
|
||||
// Introduction
|
||||
//
|
||||
// In SAML parlance an Identity Provider (IDP) is a service that knows how to authenticate users. A Service Provider (SP) is a service that delegates authentication to an IDP. If you are building a service where users log in with someone else's credentials, then you are a Service Provider. This package supports implementing both service providers and identity providers.
|
||||
//
|
||||
// The core package contains the implementation of SAML. The package samlsp provides helper middleware suitable for use in Service Provider applications. The package samlidp provides a rudimentary IDP service that is useful for testing or as a starting point for other integrations.
|
||||
//
|
||||
// Breaking Changes
|
||||
//
|
||||
// Note: between version 0.2.0 and the current master include changes to the API
|
||||
// that will break your existing code a little.
|
||||
//
|
||||
// This change turned some fields from pointers to a single optional struct into
|
||||
// the more correct slice of struct, and to pluralize the field name. For example,
|
||||
// `IDPSSODescriptor *IDPSSODescriptor` has become
|
||||
// `IDPSSODescriptors []IDPSSODescriptor`. This more accurately reflects the
|
||||
// standard.
|
||||
//
|
||||
// The struct `Metadata` has been renamed to `EntityDescriptor`. In 0.2.0 and before,
|
||||
// every struct derived from the standard has the same name as in the standard,
|
||||
// *except* for `Metadata` which should always have been called `EntityDescriptor`.
|
||||
//
|
||||
// In various places `url.URL` is now used where `string` was used <= version 0.1.0.
|
||||
//
|
||||
// In various places where keys and certificates were modeled as `string`
|
||||
// <= version 0.1.0 (what was I thinking?!) they are now modeled as
|
||||
// `*rsa.PrivateKey`, `*x509.Certificate`, or `crypto.PrivateKey` as appropriate.
|
||||
//
|
||||
// Getting Started as a Service Provider
|
||||
//
|
||||
// Let us assume we have a simple web appliation to protect. We'll modify this application so it uses SAML to authenticate users.
|
||||
// ```golang
|
||||
// package main
|
||||
//
|
||||
// import "net/http"
|
||||
//
|
||||
// func hello(w http.ResponseWriter, r *http.Request) {
|
||||
// fmt.Fprintf(w, "Hello, World!")
|
||||
// }
|
||||
//
|
||||
// func main() {
|
||||
// app := http.HandlerFunc(hello)
|
||||
// http.Handle("/hello", app)
|
||||
// http.ListenAndServe(":8000", nil)
|
||||
// }
|
||||
// ```
|
||||
// Each service provider must have an self-signed X.509 key pair established. You can generate your own with something like this:
|
||||
//
|
||||
// openssl req -x509 -newkey rsa:2048 -keyout myservice.key -out myservice.cert -days 365 -nodes -subj "/CN=myservice.example.com"
|
||||
//
|
||||
// We will use `samlsp.Middleware` to wrap the endpoint we want to protect. Middleware provides both an `http.Handler` to serve the SAML specific URLs and a set of wrappers to require the user to be logged in. We also provide the URL where the service provider can fetch the metadata from the IDP at startup. In our case, we'll use [testshib.org](https://www.testshib.org/), an identity provider designed for testing.
|
||||
//
|
||||
// ```golang
|
||||
// package main
|
||||
//
|
||||
// import (
|
||||
// "crypto/rsa"
|
||||
// "crypto/tls"
|
||||
// "crypto/x509"
|
||||
// "fmt"
|
||||
// "net/http"
|
||||
// "net/url"
|
||||
//
|
||||
// "github.com/crewjam/saml/samlsp"
|
||||
// )
|
||||
//
|
||||
// func hello(w http.ResponseWriter, r *http.Request) {
|
||||
// claims := samlsp.Claims(r.Context())
|
||||
// fmt.Fprintf(w, "Hello, %s!", claims.Attributes["cn"][0])
|
||||
// }
|
||||
//
|
||||
// func main() {
|
||||
// keyPair, err := tls.LoadX509KeyPair("myservice.cert", "myservice.key")
|
||||
// if err != nil {
|
||||
// panic(err) // TODO handle error
|
||||
// }
|
||||
// keyPair.Leaf, err = x509.ParseCertificate(keyPair.Certificate[0])
|
||||
// if err != nil {
|
||||
// panic(err) // TODO handle error
|
||||
// }
|
||||
//
|
||||
// idpMetadataURL, err := url.Parse("https://www.testshib.org/metadata/testshib-providers.xml")
|
||||
// if err != nil {
|
||||
// panic(err) // TODO handle error
|
||||
// }
|
||||
//
|
||||
// rootURL, err := url.Parse("http://localhost:8000")
|
||||
// if err != nil {
|
||||
// panic(err) // TODO handle error
|
||||
// }
|
||||
//
|
||||
// samlSP, _ := samlsp.New(samlsp.Options{
|
||||
// URL: *rootURL,
|
||||
// Key: keyPair.PrivateKey.(*rsa.PrivateKey),
|
||||
// Certificate: keyPair.Leaf,
|
||||
// IDPMetadataURL: idpMetadataURL,
|
||||
// })
|
||||
// app := http.HandlerFunc(hello)
|
||||
// http.Handle("/hello", samlSP.RequireAccount(app))
|
||||
// http.Handle("/saml/", samlSP)
|
||||
// http.ListenAndServe(":8000", nil)
|
||||
// }
|
||||
// ```
|
||||
//
|
||||
// Next we'll have to register our service provider with the identiy provider to establish trust from the service provider to the IDP. For [testshib.org](https://www.testshib.org/), you can do something like:
|
||||
//
|
||||
// mdpath=saml-test-$USER-$HOST.xml
|
||||
// curl localhost:8000/saml/metadata > $mdpath
|
||||
//
|
||||
// Naviate to https://www.testshib.org/register.html and upload the file you fetched.
|
||||
//
|
||||
// Now you should be able to authenticate. The flow should look like this:
|
||||
//
|
||||
// 1. You browse to `localhost:8000/hello`
|
||||
//
|
||||
// 1. The middleware redirects you to `https://idp.testshib.org/idp/profile/SAML2/Redirect/SSO`
|
||||
//
|
||||
// 1. testshib.org prompts you for a username and password.
|
||||
//
|
||||
// 1. testshib.org returns you an HTML document which contains an HTML form setup to POST to `localhost:8000/saml/acs`. The form is automatically submitted if you have javascript enabled.
|
||||
//
|
||||
// 1. The local service validates the response, issues a session cookie, and redirects you to the original URL, `localhost:8000/hello`.
|
||||
//
|
||||
// 1. This time when `localhost:8000/hello` is requested there is a valid session and so the main content is served.
|
||||
//
|
||||
// Getting Started as an Identity Provider
|
||||
//
|
||||
// Please see `examples/idp/` for a substantially complete example of how to use the library and helpers to be an identity provider.
|
||||
//
|
||||
// Support
|
||||
//
|
||||
// The SAML standard is huge and complex with many dark corners and strange, unused features. This package implements the most commonly used subset of these features required to provide a single sign on experience. The package supports at least the subset of SAML known as [interoperable SAML](http://saml2int.org).
|
||||
//
|
||||
// This package supports the Web SSO profile. Message flows from the service provider to the IDP are supported using the HTTP Redirect binding and the HTTP POST binding. Message flows from the IDP to the service provider are supported via the HTTP POST binding.
|
||||
//
|
||||
// The package supports signed and encrypted SAML assertions. It does not support signed or encrypted requests.
|
||||
//
|
||||
// RelayState
|
||||
//
|
||||
// The *RelayState* parameter allows you to pass user state information across the authentication flow. The most common use for this is to allow a user to request a deep link into your site, be redirected through the SAML login flow, and upon successful completion, be directed to the originaly requested link, rather than the root.
|
||||
//
|
||||
// Unfortunately, *RelayState* is less useful than it could be. Firstly, it is not authenticated, so anything you supply must be signed to avoid XSS or CSRF. Secondly, it is limited to 80 bytes in length, which precludes signing. (See section 3.6.3.1 of SAMLProfiles.)
|
||||
//
|
||||
// References
|
||||
//
|
||||
// The SAML specification is a collection of PDFs (sadly):
|
||||
//
|
||||
// - [SAMLCore](http://docs.oasis-open.org/security/saml/v2.0/saml-core-2.0-os.pdf) defines data types.
|
||||
//
|
||||
// - [SAMLBindings](http://docs.oasis-open.org/security/saml/v2.0/saml-bindings-2.0-os.pdf) defines the details of the HTTP requests in play.
|
||||
//
|
||||
// - [SAMLProfiles](http://docs.oasis-open.org/security/saml/v2.0/saml-profiles-2.0-os.pdf) describes data flows.
|
||||
//
|
||||
// - [SAMLConformance](http://docs.oasis-open.org/security/saml/v2.0/saml-conformance-2.0-os.pdf) includes a support matrix for various parts of the protocol.
|
||||
//
|
||||
// [TestShib](https://www.testshib.org/) is a testing ground for SAML service and identity providers.
|
||||
//
|
||||
// Security Issues
|
||||
//
|
||||
// Please do not report security issues in the issue tracker. Rather, please contact me directly at ross@kndr.org ([PGP Key `8EA205C01C425FF195A5E9A43FA0768F26FD2554`](https://keybase.io/crewjam)).
|
||||
package saml
|
3
vendor/github.com/crewjam/saml/saml_gen.go
generated
vendored
Normal file
3
vendor/github.com/crewjam/saml/saml_gen.go
generated
vendored
Normal file
@ -0,0 +1,3 @@
|
||||
package saml
|
||||
|
||||
//go:generate bash -c "(cat README.md | grep -E -v '^# SAML' | sed 's|^## ||g' | sed 's|\\*\\*||g' | sed 's|^|// |g'; echo 'package saml') > saml.go"
|
931
vendor/github.com/crewjam/saml/schema.go
generated
vendored
Normal file
931
vendor/github.com/crewjam/saml/schema.go
generated
vendored
Normal file
@ -0,0 +1,931 @@
|
||||
package saml
|
||||
|
||||
import (
|
||||
"encoding/xml"
|
||||
"strconv"
|
||||
"time"
|
||||
|
||||
"github.com/beevik/etree"
|
||||
"github.com/russellhaering/goxmldsig/etreeutils"
|
||||
)
|
||||
|
||||
// AuthnRequest represents the SAML object of the same name, a request from a service provider
|
||||
// to authenticate a user.
|
||||
//
|
||||
// See http://docs.oasis-open.org/security/saml/v2.0/saml-core-2.0-os.pdf
|
||||
type AuthnRequest struct {
|
||||
XMLName xml.Name `xml:"urn:oasis:names:tc:SAML:2.0:protocol AuthnRequest"`
|
||||
|
||||
ID string `xml:",attr"`
|
||||
Version string `xml:",attr"`
|
||||
IssueInstant time.Time `xml:",attr"`
|
||||
Destination string `xml:",attr"`
|
||||
Consent string `xml:",attr"`
|
||||
Issuer *Issuer `xml:"urn:oasis:names:tc:SAML:2.0:assertion Issuer"`
|
||||
Signature *etree.Element
|
||||
|
||||
Subject *Subject
|
||||
NameIDPolicy *NameIDPolicy `xml:"urn:oasis:names:tc:SAML:2.0:protocol NameIDPolicy"`
|
||||
Conditions *Conditions
|
||||
//RequestedAuthnContext *RequestedAuthnContext // TODO
|
||||
//Scoping *Scoping // TODO
|
||||
|
||||
ForceAuthn *bool `xml:",attr"`
|
||||
IsPassive *bool `xml:",attr"`
|
||||
AssertionConsumerServiceIndex string `xml:",attr"`
|
||||
AssertionConsumerServiceURL string `xml:",attr"`
|
||||
ProtocolBinding string `xml:",attr"`
|
||||
AttributeConsumingServiceIndex string `xml:",attr"`
|
||||
ProviderName string `xml:",attr"`
|
||||
}
|
||||
|
||||
// Element returns an etree.Element representing the object
|
||||
// Element returns an etree.Element representing the object in XML form.
|
||||
func (r *AuthnRequest) Element() *etree.Element {
|
||||
el := etree.NewElement("samlp:AuthnRequest")
|
||||
el.CreateAttr("xmlns:saml", "urn:oasis:names:tc:SAML:2.0:assertion")
|
||||
el.CreateAttr("xmlns:samlp", "urn:oasis:names:tc:SAML:2.0:protocol")
|
||||
el.CreateAttr("ID", r.ID)
|
||||
el.CreateAttr("Version", r.Version)
|
||||
el.CreateAttr("IssueInstant", r.IssueInstant.Format(timeFormat))
|
||||
if r.Destination != "" {
|
||||
el.CreateAttr("Destination", r.Destination)
|
||||
}
|
||||
if r.Consent != "" {
|
||||
el.CreateAttr("Consent", r.Consent)
|
||||
}
|
||||
if r.Issuer != nil {
|
||||
el.AddChild(r.Issuer.Element())
|
||||
}
|
||||
if r.Signature != nil {
|
||||
el.AddChild(r.Signature)
|
||||
}
|
||||
if r.Subject != nil {
|
||||
el.AddChild(r.Subject.Element())
|
||||
}
|
||||
if r.NameIDPolicy != nil {
|
||||
el.AddChild(r.NameIDPolicy.Element())
|
||||
}
|
||||
if r.Conditions != nil {
|
||||
el.AddChild(r.Conditions.Element())
|
||||
}
|
||||
//if r.RequestedAuthnContext != nil {
|
||||
// el.AddChild(r.RequestedAuthnContext.Element())
|
||||
//}
|
||||
//if r.Scoping != nil {
|
||||
// el.AddChild(r.Scoping.Element())
|
||||
//}
|
||||
if r.ForceAuthn != nil {
|
||||
el.CreateAttr("ForceAuthn", strconv.FormatBool(*r.ForceAuthn))
|
||||
}
|
||||
if r.IsPassive != nil {
|
||||
el.CreateAttr("IsPassive", strconv.FormatBool(*r.IsPassive))
|
||||
}
|
||||
if r.AssertionConsumerServiceIndex != "" {
|
||||
el.CreateAttr("AssertionConsumerServiceIndex", r.AssertionConsumerServiceIndex)
|
||||
}
|
||||
if r.AssertionConsumerServiceURL != "" {
|
||||
el.CreateAttr("AssertionConsumerServiceURL", r.AssertionConsumerServiceURL)
|
||||
}
|
||||
if r.ProtocolBinding != "" {
|
||||
el.CreateAttr("ProtocolBinding", r.ProtocolBinding)
|
||||
}
|
||||
if r.AttributeConsumingServiceIndex != "" {
|
||||
el.CreateAttr("AttributeConsumingServiceIndex", r.AttributeConsumingServiceIndex)
|
||||
}
|
||||
if r.ProviderName != "" {
|
||||
el.CreateAttr("ProviderName", r.ProviderName)
|
||||
}
|
||||
return el
|
||||
}
|
||||
|
||||
// MarshalXML implements xml.Marshaler
|
||||
func (r *AuthnRequest) MarshalXML(e *xml.Encoder, start xml.StartElement) error {
|
||||
type Alias AuthnRequest
|
||||
aux := &struct {
|
||||
IssueInstant RelaxedTime `xml:",attr"`
|
||||
*Alias
|
||||
}{
|
||||
IssueInstant: RelaxedTime(r.IssueInstant),
|
||||
Alias: (*Alias)(r),
|
||||
}
|
||||
return e.Encode(aux)
|
||||
}
|
||||
|
||||
// UnmarshalXML implements xml.Unmarshaler
|
||||
func (r *AuthnRequest) UnmarshalXML(d *xml.Decoder, start xml.StartElement) error {
|
||||
type Alias AuthnRequest
|
||||
aux := &struct {
|
||||
IssueInstant RelaxedTime `xml:",attr"`
|
||||
*Alias
|
||||
}{
|
||||
Alias: (*Alias)(r),
|
||||
}
|
||||
if err := d.DecodeElement(&aux, &start); err != nil {
|
||||
return err
|
||||
}
|
||||
r.IssueInstant = time.Time(aux.IssueInstant)
|
||||
return nil
|
||||
}
|
||||
|
||||
// Issuer represents the SAML object of the same name.
|
||||
//
|
||||
// See http://docs.oasis-open.org/security/saml/v2.0/saml-core-2.0-os.pdf
|
||||
type Issuer struct {
|
||||
XMLName xml.Name `xml:"urn:oasis:names:tc:SAML:2.0:assertion Issuer"`
|
||||
NameQualifier string `xml:",attr"`
|
||||
SPNameQualifier string `xml:",attr"`
|
||||
Format string `xml:",attr"`
|
||||
SPProvidedID string `xml:",attr"`
|
||||
Value string `xml:",chardata"`
|
||||
}
|
||||
|
||||
// Element returns an etree.Element representing the object in XML form.
|
||||
func (a *Issuer) Element() *etree.Element {
|
||||
el := etree.NewElement("saml:Issuer")
|
||||
if a.NameQualifier != "" {
|
||||
el.CreateAttr("NameQualifier", a.NameQualifier)
|
||||
}
|
||||
if a.SPNameQualifier != "" {
|
||||
el.CreateAttr("SPNameQualifier", a.SPNameQualifier)
|
||||
}
|
||||
if a.Format != "" {
|
||||
el.CreateAttr("Format", a.Format)
|
||||
}
|
||||
if a.SPProvidedID != "" {
|
||||
el.CreateAttr("SPProvidedID", a.SPProvidedID)
|
||||
}
|
||||
el.SetText(a.Value)
|
||||
return el
|
||||
}
|
||||
|
||||
// NameIDPolicy represents the SAML object of the same name.
|
||||
//
|
||||
// See http://docs.oasis-open.org/security/saml/v2.0/saml-core-2.0-os.pdf
|
||||
type NameIDPolicy struct {
|
||||
XMLName xml.Name `xml:"urn:oasis:names:tc:SAML:2.0:protocol NameIDPolicy"`
|
||||
Format *string `xml:",attr"`
|
||||
SPNameQualifier *string `xml:",attr"`
|
||||
AllowCreate *bool `xml:",attr"`
|
||||
}
|
||||
|
||||
// Element returns an etree.Element representing the object in XML form.
|
||||
func (a *NameIDPolicy) Element() *etree.Element {
|
||||
el := etree.NewElement("samlp:NameIDPolicy")
|
||||
if a.Format != nil {
|
||||
el.CreateAttr("Format", *a.Format)
|
||||
}
|
||||
if a.SPNameQualifier != nil {
|
||||
el.CreateAttr("SPNameQualifier", *a.SPNameQualifier)
|
||||
}
|
||||
if a.AllowCreate != nil {
|
||||
el.CreateAttr("AllowCreate", strconv.FormatBool(*a.AllowCreate))
|
||||
}
|
||||
return el
|
||||
}
|
||||
|
||||
// Response represents the SAML object of the same name.
|
||||
//
|
||||
// See http://docs.oasis-open.org/security/saml/v2.0/saml-core-2.0-os.pdf
|
||||
type Response struct {
|
||||
XMLName xml.Name `xml:"urn:oasis:names:tc:SAML:2.0:protocol Response"`
|
||||
ID string `xml:",attr"`
|
||||
InResponseTo string `xml:",attr"`
|
||||
Version string `xml:",attr"`
|
||||
IssueInstant time.Time `xml:",attr"`
|
||||
Destination string `xml:",attr"`
|
||||
Consent string `xml:",attr"`
|
||||
Issuer *Issuer `xml:"urn:oasis:names:tc:SAML:2.0:assertion Issuer"`
|
||||
Signature *etree.Element
|
||||
Status Status `xml:"urn:oasis:names:tc:SAML:2.0:protocol Status"`
|
||||
|
||||
// TODO(ross): more than one EncryptedAssertion is allowed
|
||||
EncryptedAssertion *etree.Element `xml:"urn:oasis:names:tc:SAML:2.0:assertion EncryptedAssertion"`
|
||||
|
||||
// TODO(ross): more than one Assertion is allowed
|
||||
Assertion *Assertion `xml:"urn:oasis:names:tc:SAML:2.0:assertion Assertion"`
|
||||
}
|
||||
|
||||
// Element returns an etree.Element representing the object in XML form.
|
||||
func (r *Response) Element() *etree.Element {
|
||||
el := etree.NewElement("samlp:Response")
|
||||
el.CreateAttr("xmlns:saml", "urn:oasis:names:tc:SAML:2.0:assertion")
|
||||
el.CreateAttr("xmlns:samlp", "urn:oasis:names:tc:SAML:2.0:protocol")
|
||||
|
||||
// Note: This namespace is not used by any element or attribute name, but
|
||||
// is required so that the AttributeValue type element can have a value like
|
||||
// "xs:string". If we don't declare it here, then it will be stripped by the
|
||||
// cannonicalizer. This could be avoided by providing a prefix list to the
|
||||
// cannonicalizer, but prefix lists do not appear to be implemented correctly
|
||||
// in some libraries, so the safest action is to always produce XML that is
|
||||
// (a) in cannonical form and (b) does not require prefix lists.
|
||||
el.CreateAttr("xmlns:xs", "http://www.w3.org/2001/XMLSchema")
|
||||
|
||||
el.CreateAttr("ID", r.ID)
|
||||
if r.InResponseTo != "" {
|
||||
el.CreateAttr("InResponseTo", r.InResponseTo)
|
||||
}
|
||||
el.CreateAttr("Version", r.Version)
|
||||
el.CreateAttr("IssueInstant", r.IssueInstant.Format(timeFormat))
|
||||
if r.Destination != "" {
|
||||
el.CreateAttr("Destination", r.Destination)
|
||||
}
|
||||
if r.Consent != "" {
|
||||
el.CreateAttr("Consent", r.Consent)
|
||||
}
|
||||
if r.Issuer != nil {
|
||||
el.AddChild(r.Issuer.Element())
|
||||
}
|
||||
if r.Signature != nil {
|
||||
el.AddChild(r.Signature)
|
||||
}
|
||||
el.AddChild(r.Status.Element())
|
||||
if r.EncryptedAssertion != nil {
|
||||
el.AddChild(r.EncryptedAssertion)
|
||||
}
|
||||
if r.Assertion != nil {
|
||||
el.AddChild(r.Assertion.Element())
|
||||
}
|
||||
return el
|
||||
}
|
||||
|
||||
// MarshalXML implements xml.Marshaler
|
||||
func (r *Response) MarshalXML(e *xml.Encoder, start xml.StartElement) error {
|
||||
type Alias Response
|
||||
aux := &struct {
|
||||
IssueInstant RelaxedTime `xml:",attr"`
|
||||
*Alias
|
||||
}{
|
||||
IssueInstant: RelaxedTime(r.IssueInstant),
|
||||
Alias: (*Alias)(r),
|
||||
}
|
||||
return e.Encode(aux)
|
||||
}
|
||||
|
||||
// UnmarshalXML implements xml.Unmarshaler
|
||||
func (r *Response) UnmarshalXML(d *xml.Decoder, start xml.StartElement) error {
|
||||
type Alias Response
|
||||
aux := &struct {
|
||||
IssueInstant RelaxedTime `xml:",attr"`
|
||||
*Alias
|
||||
}{
|
||||
Alias: (*Alias)(r),
|
||||
}
|
||||
if err := d.DecodeElement(&aux, &start); err != nil {
|
||||
return err
|
||||
}
|
||||
r.IssueInstant = time.Time(aux.IssueInstant)
|
||||
return nil
|
||||
}
|
||||
|
||||
// Status represents the SAML object of the same name.
|
||||
//
|
||||
// See http://docs.oasis-open.org/security/saml/v2.0/saml-core-2.0-os.pdf
|
||||
type Status struct {
|
||||
XMLName xml.Name `xml:"urn:oasis:names:tc:SAML:2.0:protocol Status"`
|
||||
StatusCode StatusCode
|
||||
StatusMessage *StatusMessage
|
||||
StatusDetail *StatusDetail
|
||||
}
|
||||
|
||||
// Element returns an etree.Element representing the object in XML form.
|
||||
func (s *Status) Element() *etree.Element {
|
||||
el := etree.NewElement("samlp:Status")
|
||||
el.AddChild(s.StatusCode.Element())
|
||||
if s.StatusMessage != nil {
|
||||
el.AddChild(s.StatusMessage.Element())
|
||||
}
|
||||
if s.StatusDetail != nil {
|
||||
el.AddChild(s.StatusDetail.Element())
|
||||
}
|
||||
return el
|
||||
}
|
||||
|
||||
// StatusCode represents the SAML object of the same name.
|
||||
//
|
||||
// See http://docs.oasis-open.org/security/saml/v2.0/saml-core-2.0-os.pdf
|
||||
type StatusCode struct {
|
||||
XMLName xml.Name `xml:"urn:oasis:names:tc:SAML:2.0:protocol StatusCode"`
|
||||
Value string `xml:",attr"`
|
||||
StatusCode *StatusCode
|
||||
}
|
||||
|
||||
// Element returns an etree.Element representing the object in XML form.
|
||||
func (s *StatusCode) Element() *etree.Element {
|
||||
el := etree.NewElement("samlp:StatusCode")
|
||||
el.CreateAttr("Value", s.Value)
|
||||
if s.StatusCode != nil {
|
||||
el.AddChild(s.StatusCode.Element())
|
||||
}
|
||||
return el
|
||||
}
|
||||
|
||||
// StatusSuccess means the request succeeded. Additional information MAY be returned in the <StatusMessage> and/or <StatusDetail> elements.
|
||||
//
|
||||
// TODO(ross): this value is mostly constant, but is mutated in tests. Fix the hacky test so this can be const.
|
||||
var StatusSuccess = "urn:oasis:names:tc:SAML:2.0:status:Success"
|
||||
|
||||
const (
|
||||
// The permissible top-level <StatusCode> values are as follows:
|
||||
|
||||
// StatusRequester means the request could not be performed due to an error on the part of the requester.
|
||||
StatusRequester = "urn:oasis:names:tc:SAML:2.0:status:Requester"
|
||||
|
||||
// StatusResponder means the request could not be performed due to an error on the part of the SAML responder or SAML authority.
|
||||
StatusResponder = "urn:oasis:names:tc:SAML:2.0:status:Responder"
|
||||
|
||||
// StatusVersionMismatch means the SAML responder could not process the request because the version of the request message was incorrect.
|
||||
StatusVersionMismatch = "urn:oasis:names:tc:SAML:2.0:status:VersionMismatch"
|
||||
|
||||
// The following second-level status codes are referenced at various places in this specification. Additional
|
||||
// second-level status codes MAY be defined in future versions of the SAML specification. System entities
|
||||
// are free to define more specific status codes by defining appropriate URI references.
|
||||
|
||||
// StatusAuthnFailed means the responding provider was unable to successfully authenticate the principal.
|
||||
StatusAuthnFailed = "urn:oasis:names:tc:SAML:2.0:status:AuthnFailed"
|
||||
|
||||
// StatusInvalidAttrNameOrValue means Unexpected or invalid content was encountered within a <saml:Attribute> or <saml:AttributeValue> element.
|
||||
StatusInvalidAttrNameOrValue = "urn:oasis:names:tc:SAML:2.0:status:InvalidAttrNameOrValue"
|
||||
|
||||
// StatusInvalidNameIDPolicy means the responding provider cannot or will not support the requested name identifier policy.
|
||||
StatusInvalidNameIDPolicy = "urn:oasis:names:tc:SAML:2.0:status:InvalidNameIDPolicy"
|
||||
|
||||
// StatusNoAuthnContext means the specified authentication context requirements cannot be met by the responder.
|
||||
StatusNoAuthnContext = "urn:oasis:names:tc:SAML:2.0:status:NoAuthnContext"
|
||||
|
||||
// StatusNoAvailableIDP is used by an intermediary to indicate that none of the supported identity provider <Loc> elements in an <IDPList> can be resolved or that none of the supported identity providers are available.
|
||||
StatusNoAvailableIDP = "urn:oasis:names:tc:SAML:2.0:status:NoAvailableIDP"
|
||||
|
||||
// StatusNoPassive means Indicates the responding provider cannot authenticate the principal passively, as has been requested.
|
||||
StatusNoPassive = "urn:oasis:names:tc:SAML:2.0:status:NoPassive"
|
||||
|
||||
// StatusNoSupportedIDP is used by an intermediary to indicate that none of the identity providers in an <IDPList> are supported by the intermediary.
|
||||
StatusNoSupportedIDP = "urn:oasis:names:tc:SAML:2.0:status:NoSupportedIDP"
|
||||
|
||||
// StatusPartialLogout is used by a session authority to indicate to a session participant that it was not able to propagate logout to all other session participants.
|
||||
StatusPartialLogout = "urn:oasis:names:tc:SAML:2.0:status:PartialLogout"
|
||||
|
||||
// StatusProxyCountExceeded means Indicates that a responding provider cannot authenticate the principal directly and is not permitted to proxy the request further.
|
||||
StatusProxyCountExceeded = "urn:oasis:names:tc:SAML:2.0:status:ProxyCountExceeded"
|
||||
|
||||
// StatusRequestDenied means the SAML responder or SAML authority is able to process the request but has chosen not to respond. This status code MAY be used when there is concern about the security context of the request message or the sequence of request messages received from a particular requester.
|
||||
StatusRequestDenied = "urn:oasis:names:tc:SAML:2.0:status:RequestDenied"
|
||||
|
||||
// StatusRequestUnsupported means the SAML responder or SAML authority does not support the request.
|
||||
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"
|
||||
|
||||
// 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"
|
||||
|
||||
// StatusRequestVersionTooLow means the SAML responder cannot process the request because the protocol version specified in the request message is too low.
|
||||
StatusRequestVersionTooLow = "urn:oasis:names:tc:SAML:2.0:status:RequestVersionTooLow"
|
||||
|
||||
// StatusResourceNotRecognized means the resource value provided in the request message is invalid or unrecognized.
|
||||
StatusResourceNotRecognized = "urn:oasis:names:tc:SAML:2.0:status:ResourceNotRecognized"
|
||||
|
||||
// StatusTooManyResponses means the response message would contain more elements than the SAML responder is able to return.
|
||||
StatusTooManyResponses = "urn:oasis:names:tc:SAML:2.0:status:TooManyResponses"
|
||||
|
||||
// StatusUnknownAttrProfile means an entity that has no knowledge of a particular attribute profile has been presented with an attribute means drawn from that profile.
|
||||
StatusUnknownAttrProfile = "urn:oasis:names:tc:SAML:2.0:status:UnknownAttrProfile"
|
||||
|
||||
// StatusUnknownPrincipal means the responding provider does not recognize the principal specified or implied by the request.
|
||||
StatusUnknownPrincipal = "urn:oasis:names:tc:SAML:2.0:status:UnknownPrincipal"
|
||||
|
||||
// StatusUnsupportedBinding means the SAML responder cannot properly fulfill the request using the protocol binding specified in the request.
|
||||
StatusUnsupportedBinding = "urn:oasis:names:tc:SAML:2.0:status:UnsupportedBinding"
|
||||
)
|
||||
|
||||
// StatusMessage represents the SAML element StatusMessage.
|
||||
//
|
||||
// See http://docs.oasis-open.org/security/saml/v2.0/saml-core-2.0-os.pdf §3.2.2.3
|
||||
type StatusMessage struct {
|
||||
Value string
|
||||
}
|
||||
|
||||
// Element returns an etree.Element representing the object in XML form.
|
||||
func (sm StatusMessage) Element() *etree.Element {
|
||||
el := etree.NewElement("samlp:StatusMessage")
|
||||
el.SetText(sm.Value)
|
||||
return el
|
||||
}
|
||||
|
||||
// StatusDetail represents the SAML element StatusDetail.
|
||||
//
|
||||
// See http://docs.oasis-open.org/security/saml/v2.0/saml-core-2.0-os.pdf §3.2.2.4
|
||||
type StatusDetail struct {
|
||||
Children []*etree.Element
|
||||
}
|
||||
|
||||
// Element returns an etree.Element representing the object in XML form.
|
||||
func (sm StatusDetail) Element() *etree.Element {
|
||||
el := etree.NewElement("samlp:StatusDetail")
|
||||
for _, child := range sm.Children {
|
||||
el.AddChild(child)
|
||||
}
|
||||
return el
|
||||
}
|
||||
|
||||
// Assertion represents the SAML element Assertion.
|
||||
//
|
||||
// See http://docs.oasis-open.org/security/saml/v2.0/saml-core-2.0-os.pdf §2.3.3
|
||||
type Assertion struct {
|
||||
XMLName xml.Name `xml:"urn:oasis:names:tc:SAML:2.0:assertion Assertion"`
|
||||
ID string `xml:",attr"`
|
||||
IssueInstant time.Time `xml:",attr"`
|
||||
Version string `xml:",attr"`
|
||||
Issuer Issuer `xml:"urn:oasis:names:tc:SAML:2.0:assertion Issuer"`
|
||||
Signature *etree.Element
|
||||
Subject *Subject
|
||||
Conditions *Conditions
|
||||
// Advice *Advice
|
||||
// Statements []Statement
|
||||
AuthnStatements []AuthnStatement `xml:"AuthnStatement"`
|
||||
// AuthzDecisionStatements []AuthzDecisionStatement
|
||||
AttributeStatements []AttributeStatement `xml:"AttributeStatement"`
|
||||
}
|
||||
|
||||
// Element returns an etree.Element representing the object in XML form.
|
||||
func (a *Assertion) Element() *etree.Element {
|
||||
el := etree.NewElement("saml:Assertion")
|
||||
el.CreateAttr("xmlns:saml", "urn:oasis:names:tc:SAML:2.0:assertion")
|
||||
el.CreateAttr("Version", "2.0")
|
||||
el.CreateAttr("ID", a.ID)
|
||||
el.CreateAttr("IssueInstant", a.IssueInstant.Format(timeFormat))
|
||||
el.AddChild(a.Issuer.Element())
|
||||
if a.Signature != nil {
|
||||
el.AddChild(a.Signature)
|
||||
}
|
||||
if a.Subject != nil {
|
||||
el.AddChild(a.Subject.Element())
|
||||
}
|
||||
if a.Conditions != nil {
|
||||
el.AddChild(a.Conditions.Element())
|
||||
}
|
||||
for _, authnStatement := range a.AuthnStatements {
|
||||
el.AddChild(authnStatement.Element())
|
||||
}
|
||||
for _, attributeStatement := range a.AttributeStatements {
|
||||
el.AddChild(attributeStatement.Element())
|
||||
}
|
||||
err := etreeutils.TransformExcC14n(el, canonicalizerPrefixList)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return el
|
||||
}
|
||||
|
||||
// UnmarshalXML implements xml.Unmarshaler
|
||||
func (a *Assertion) UnmarshalXML(d *xml.Decoder, start xml.StartElement) error {
|
||||
type Alias Assertion
|
||||
aux := &struct {
|
||||
IssueInstant RelaxedTime `xml:",attr"`
|
||||
*Alias
|
||||
}{
|
||||
Alias: (*Alias)(a),
|
||||
}
|
||||
if err := d.DecodeElement(&aux, &start); err != nil {
|
||||
return err
|
||||
}
|
||||
a.IssueInstant = time.Time(aux.IssueInstant)
|
||||
return nil
|
||||
}
|
||||
|
||||
// Subject represents the SAML element Subject.
|
||||
//
|
||||
// See http://docs.oasis-open.org/security/saml/v2.0/saml-core-2.0-os.pdf §2.4.1
|
||||
type Subject struct {
|
||||
XMLName xml.Name `xml:"urn:oasis:names:tc:SAML:2.0:assertion Subject"`
|
||||
// BaseID *BaseID ... TODO
|
||||
NameID *NameID
|
||||
// EncryptedID *EncryptedID ... TODO
|
||||
SubjectConfirmations []SubjectConfirmation `xml:"SubjectConfirmation"`
|
||||
}
|
||||
|
||||
// Element returns an etree.Element representing the object in XML form.
|
||||
func (a *Subject) Element() *etree.Element {
|
||||
el := etree.NewElement("saml:Subject")
|
||||
if a.NameID != nil {
|
||||
el.AddChild(a.NameID.Element())
|
||||
}
|
||||
for _, v := range a.SubjectConfirmations {
|
||||
el.AddChild(v.Element())
|
||||
}
|
||||
return el
|
||||
}
|
||||
|
||||
// NameID represents the SAML element NameID.
|
||||
//
|
||||
// See http://docs.oasis-open.org/security/saml/v2.0/saml-core-2.0-os.pdf §2.2.3
|
||||
type NameID struct {
|
||||
NameQualifier string `xml:",attr"`
|
||||
SPNameQualifier string `xml:",attr"`
|
||||
Format string `xml:",attr"`
|
||||
SPProvidedID string `xml:",attr"`
|
||||
Value string `xml:",chardata"`
|
||||
}
|
||||
|
||||
// Element returns an etree.Element representing the object in XML form.
|
||||
func (a *NameID) Element() *etree.Element {
|
||||
el := etree.NewElement("saml:NameID")
|
||||
if a.NameQualifier != "" {
|
||||
el.CreateAttr("NameQualifier", a.NameQualifier)
|
||||
}
|
||||
if a.SPNameQualifier != "" {
|
||||
el.CreateAttr("SPNameQualifier", a.SPNameQualifier)
|
||||
}
|
||||
if a.Format != "" {
|
||||
el.CreateAttr("Format", a.Format)
|
||||
}
|
||||
if a.SPProvidedID != "" {
|
||||
el.CreateAttr("SPProvidedID", a.SPProvidedID)
|
||||
}
|
||||
if a.Value != "" {
|
||||
el.SetText(a.Value)
|
||||
}
|
||||
return el
|
||||
}
|
||||
|
||||
// SubjectConfirmation represents the SAML element SubjectConfirmation.
|
||||
//
|
||||
// See http://docs.oasis-open.org/security/saml/v2.0/saml-core-2.0-os.pdf §2.4.1.1
|
||||
type SubjectConfirmation struct {
|
||||
Method string `xml:",attr"`
|
||||
// BaseID *BaseID ... TODO
|
||||
NameID *NameID
|
||||
// EncryptedID *EncryptedID ... TODO
|
||||
SubjectConfirmationData *SubjectConfirmationData
|
||||
}
|
||||
|
||||
// Element returns an etree.Element representing the object in XML form.
|
||||
func (a *SubjectConfirmation) Element() *etree.Element {
|
||||
el := etree.NewElement("saml:SubjectConfirmation")
|
||||
el.CreateAttr("Method", a.Method)
|
||||
if a.NameID != nil {
|
||||
el.AddChild(a.NameID.Element())
|
||||
}
|
||||
if a.SubjectConfirmationData != nil {
|
||||
el.AddChild(a.SubjectConfirmationData.Element())
|
||||
}
|
||||
return el
|
||||
}
|
||||
|
||||
// SubjectConfirmationData represents the SAML element SubjectConfirmationData.
|
||||
//
|
||||
// See http://docs.oasis-open.org/security/saml/v2.0/saml-core-2.0-os.pdf §2.4.1.2
|
||||
type SubjectConfirmationData struct {
|
||||
NotBefore time.Time `xml:",attr"`
|
||||
NotOnOrAfter time.Time `xml:",attr"`
|
||||
Recipient string `xml:",attr"`
|
||||
InResponseTo string `xml:",attr"`
|
||||
Address string `xml:",attr"`
|
||||
}
|
||||
|
||||
// Element returns an etree.Element representing the object in XML form.
|
||||
func (s *SubjectConfirmationData) Element() *etree.Element {
|
||||
el := etree.NewElement("saml:SubjectConfirmationData")
|
||||
if !s.NotBefore.IsZero() {
|
||||
el.CreateAttr("NotBefore", s.NotBefore.Format(timeFormat))
|
||||
}
|
||||
if !s.NotOnOrAfter.IsZero() {
|
||||
el.CreateAttr("NotOnOrAfter", s.NotOnOrAfter.Format(timeFormat))
|
||||
}
|
||||
if s.Recipient != "" {
|
||||
el.CreateAttr("Recipient", s.Recipient)
|
||||
}
|
||||
if s.InResponseTo != "" {
|
||||
el.CreateAttr("InResponseTo", s.InResponseTo)
|
||||
}
|
||||
if s.Address != "" {
|
||||
el.CreateAttr("Address", s.Address)
|
||||
}
|
||||
return el
|
||||
}
|
||||
|
||||
// MarshalXML implements xml.Marshaler
|
||||
func (s *SubjectConfirmationData) MarshalXML(e *xml.Encoder, start xml.StartElement) error {
|
||||
type Alias SubjectConfirmationData
|
||||
aux := &struct {
|
||||
NotOnOrAfter RelaxedTime `xml:",attr"`
|
||||
*Alias
|
||||
}{
|
||||
NotOnOrAfter: RelaxedTime(s.NotOnOrAfter),
|
||||
Alias: (*Alias)(s),
|
||||
}
|
||||
return e.EncodeElement(aux, start)
|
||||
}
|
||||
|
||||
// UnmarshalXML implements xml.Unmarshaler
|
||||
func (s *SubjectConfirmationData) UnmarshalXML(d *xml.Decoder, start xml.StartElement) error {
|
||||
type Alias SubjectConfirmationData
|
||||
aux := &struct {
|
||||
NotOnOrAfter RelaxedTime `xml:",attr"`
|
||||
*Alias
|
||||
}{
|
||||
Alias: (*Alias)(s),
|
||||
}
|
||||
if err := d.DecodeElement(&aux, &start); err != nil {
|
||||
return err
|
||||
}
|
||||
s.NotOnOrAfter = time.Time(aux.NotOnOrAfter)
|
||||
return nil
|
||||
}
|
||||
|
||||
// Conditions represents the SAML element Conditions.
|
||||
//
|
||||
// See http://docs.oasis-open.org/security/saml/v2.0/saml-core-2.0-os.pdf §2.5.1
|
||||
type Conditions struct {
|
||||
NotBefore time.Time `xml:",attr"`
|
||||
NotOnOrAfter time.Time `xml:",attr"`
|
||||
AudienceRestrictions []AudienceRestriction `xml:"AudienceRestriction"`
|
||||
OneTimeUse *OneTimeUse
|
||||
ProxyRestriction *ProxyRestriction
|
||||
}
|
||||
|
||||
// Element returns an etree.Element representing the object in XML form.
|
||||
func (c *Conditions) Element() *etree.Element {
|
||||
el := etree.NewElement("saml:Conditions")
|
||||
if !c.NotBefore.IsZero() {
|
||||
el.CreateAttr("NotBefore", c.NotBefore.Format(timeFormat))
|
||||
}
|
||||
if !c.NotOnOrAfter.IsZero() {
|
||||
el.CreateAttr("NotOnOrAfter", c.NotOnOrAfter.Format(timeFormat))
|
||||
}
|
||||
for _, v := range c.AudienceRestrictions {
|
||||
el.AddChild(v.Element())
|
||||
}
|
||||
if c.OneTimeUse != nil {
|
||||
el.AddChild(c.OneTimeUse.Element())
|
||||
}
|
||||
if c.ProxyRestriction != nil {
|
||||
el.AddChild(c.ProxyRestriction.Element())
|
||||
}
|
||||
return el
|
||||
}
|
||||
|
||||
// MarshalXML implements xml.Marshaler
|
||||
func (c *Conditions) MarshalXML(e *xml.Encoder, start xml.StartElement) error {
|
||||
type Alias Conditions
|
||||
aux := &struct {
|
||||
NotBefore RelaxedTime `xml:",attr"`
|
||||
NotOnOrAfter RelaxedTime `xml:",attr"`
|
||||
*Alias
|
||||
}{
|
||||
NotBefore: RelaxedTime(c.NotBefore),
|
||||
NotOnOrAfter: RelaxedTime(c.NotOnOrAfter),
|
||||
Alias: (*Alias)(c),
|
||||
}
|
||||
return e.EncodeElement(aux, start)
|
||||
}
|
||||
|
||||
// UnmarshalXML implements xml.Unmarshaler
|
||||
func (c *Conditions) UnmarshalXML(d *xml.Decoder, start xml.StartElement) error {
|
||||
type Alias Conditions
|
||||
aux := &struct {
|
||||
NotBefore RelaxedTime `xml:",attr"`
|
||||
NotOnOrAfter RelaxedTime `xml:",attr"`
|
||||
*Alias
|
||||
}{
|
||||
Alias: (*Alias)(c),
|
||||
}
|
||||
if err := d.DecodeElement(&aux, &start); err != nil {
|
||||
return err
|
||||
}
|
||||
c.NotBefore = time.Time(aux.NotBefore)
|
||||
c.NotOnOrAfter = time.Time(aux.NotOnOrAfter)
|
||||
return nil
|
||||
}
|
||||
|
||||
// AudienceRestriction represents the SAML element AudienceRestriction.
|
||||
//
|
||||
// See http://docs.oasis-open.org/security/saml/v2.0/saml-core-2.0-os.pdf §2.5.1.4
|
||||
type AudienceRestriction struct {
|
||||
Audience Audience
|
||||
}
|
||||
|
||||
// Element returns an etree.Element representing the object in XML form.
|
||||
func (a *AudienceRestriction) Element() *etree.Element {
|
||||
el := etree.NewElement("saml:AudienceRestriction")
|
||||
el.AddChild(a.Audience.Element())
|
||||
return el
|
||||
}
|
||||
|
||||
// Audience represents the SAML element Audience.
|
||||
//
|
||||
// See http://docs.oasis-open.org/security/saml/v2.0/saml-core-2.0-os.pdf §2.5.1.4
|
||||
type Audience struct {
|
||||
Value string `xml:",chardata"`
|
||||
}
|
||||
|
||||
// Element returns an etree.Element representing the object in XML form.
|
||||
func (a *Audience) Element() *etree.Element {
|
||||
el := etree.NewElement("saml:Audience")
|
||||
el.SetText(a.Value)
|
||||
return el
|
||||
}
|
||||
|
||||
// OneTimeUse represents the SAML element OneTimeUse.
|
||||
//
|
||||
// See http://docs.oasis-open.org/security/saml/v2.0/saml-core-2.0-os.pdf §2.5.1.5
|
||||
type OneTimeUse struct{}
|
||||
|
||||
// Element returns an etree.Element representing the object in XML form.
|
||||
func (a *OneTimeUse) Element() *etree.Element {
|
||||
return etree.NewElement("saml:OneTimeUse")
|
||||
}
|
||||
|
||||
// ProxyRestriction represents the SAML element ProxyRestriction.
|
||||
//
|
||||
// See http://docs.oasis-open.org/security/saml/v2.0/saml-core-2.0-os.pdf §2.5.1.6
|
||||
type ProxyRestriction struct {
|
||||
Count *int
|
||||
Audiences []Audience
|
||||
}
|
||||
|
||||
// Element returns an etree.Element representing the object in XML form.
|
||||
func (a *ProxyRestriction) Element() *etree.Element {
|
||||
el := etree.NewElement("saml:ProxyRestriction")
|
||||
if a.Count != nil {
|
||||
el.CreateAttr("Count", strconv.Itoa(*a.Count))
|
||||
}
|
||||
for _, v := range a.Audiences {
|
||||
el.AddChild(v.Element())
|
||||
}
|
||||
return el
|
||||
}
|
||||
|
||||
// AuthnStatement represents the SAML element AuthnStatement.
|
||||
//
|
||||
// See http://docs.oasis-open.org/security/saml/v2.0/saml-core-2.0-os.pdf §2.7.2
|
||||
type AuthnStatement struct {
|
||||
AuthnInstant time.Time `xml:",attr"`
|
||||
SessionIndex string `xml:",attr"`
|
||||
SessionNotOnOrAfter *time.Time `xml:",attr"`
|
||||
SubjectLocality *SubjectLocality
|
||||
AuthnContext AuthnContext
|
||||
}
|
||||
|
||||
// Element returns an etree.Element representing the object in XML form.
|
||||
func (a *AuthnStatement) Element() *etree.Element {
|
||||
el := etree.NewElement("saml:AuthnStatement")
|
||||
el.CreateAttr("AuthnInstant", a.AuthnInstant.Format(timeFormat))
|
||||
if a.SessionIndex != "" {
|
||||
el.CreateAttr("SessionIndex", a.SessionIndex)
|
||||
}
|
||||
if a.SubjectLocality != nil {
|
||||
el.AddChild(a.SubjectLocality.Element())
|
||||
}
|
||||
el.AddChild(a.AuthnContext.Element())
|
||||
return el
|
||||
}
|
||||
|
||||
// MarshalXML implements xml.Marshaler
|
||||
func (a *AuthnStatement) MarshalXML(e *xml.Encoder, start xml.StartElement) error {
|
||||
type Alias AuthnStatement
|
||||
aux := &struct {
|
||||
AuthnInstant RelaxedTime `xml:",attr"`
|
||||
*Alias
|
||||
}{
|
||||
AuthnInstant: RelaxedTime(a.AuthnInstant),
|
||||
Alias: (*Alias)(a),
|
||||
}
|
||||
return e.EncodeElement(aux, start)
|
||||
}
|
||||
|
||||
// UnmarshalXML implements xml.Unmarshaler
|
||||
func (a *AuthnStatement) UnmarshalXML(d *xml.Decoder, start xml.StartElement) error {
|
||||
type Alias AuthnStatement
|
||||
aux := &struct {
|
||||
AuthnInstant RelaxedTime `xml:",attr"`
|
||||
*Alias
|
||||
}{
|
||||
Alias: (*Alias)(a),
|
||||
}
|
||||
if err := d.DecodeElement(&aux, &start); err != nil {
|
||||
return err
|
||||
}
|
||||
a.AuthnInstant = time.Time(aux.AuthnInstant)
|
||||
return nil
|
||||
}
|
||||
|
||||
// SubjectLocality represents the SAML element SubjectLocality.
|
||||
//
|
||||
// See http://docs.oasis-open.org/security/saml/v2.0/saml-core-2.0-os.pdf §2.7.2.1
|
||||
type SubjectLocality struct {
|
||||
Address string `xml:",attr"`
|
||||
DNSName string `xml:",attr"`
|
||||
}
|
||||
|
||||
// Element returns an etree.Element representing the object in XML form.
|
||||
func (a *SubjectLocality) Element() *etree.Element {
|
||||
el := etree.NewElement("saml:SubjectLocality")
|
||||
if a.Address != "" {
|
||||
el.CreateAttr("Address", a.Address)
|
||||
}
|
||||
if a.DNSName != "" {
|
||||
el.CreateAttr("DNSName", a.DNSName)
|
||||
}
|
||||
return el
|
||||
}
|
||||
|
||||
// AuthnContext represents the SAML element AuthnContext.
|
||||
//
|
||||
// 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
|
||||
}
|
||||
|
||||
// Element returns an etree.Element representing the object in XML form.
|
||||
func (a *AuthnContext) Element() *etree.Element {
|
||||
el := etree.NewElement("saml:AuthnContext")
|
||||
if a.AuthnContextClassRef != nil {
|
||||
el.AddChild(a.AuthnContextClassRef.Element())
|
||||
}
|
||||
return el
|
||||
}
|
||||
|
||||
// AuthnContextClassRef represents the SAML element AuthnContextClassRef.
|
||||
//
|
||||
// See http://docs.oasis-open.org/security/saml/v2.0/saml-core-2.0-os.pdf §2.7.2.2
|
||||
type AuthnContextClassRef struct {
|
||||
Value string `xml:",chardata"`
|
||||
}
|
||||
|
||||
// Element returns an etree.Element representing the object in XML form.
|
||||
func (a *AuthnContextClassRef) Element() *etree.Element {
|
||||
el := etree.NewElement("saml:AuthnContextClassRef")
|
||||
el.SetText(a.Value)
|
||||
return el
|
||||
}
|
||||
|
||||
// AttributeStatement represents the SAML element AttributeStatement.
|
||||
//
|
||||
// See http://docs.oasis-open.org/security/saml/v2.0/saml-core-2.0-os.pdf §2.7.3
|
||||
type AttributeStatement struct {
|
||||
Attributes []Attribute `xml:"Attribute"`
|
||||
}
|
||||
|
||||
// Element returns an etree.Element representing the object in XML form.
|
||||
func (a *AttributeStatement) Element() *etree.Element {
|
||||
el := etree.NewElement("saml:AttributeStatement")
|
||||
for _, v := range a.Attributes {
|
||||
el.AddChild(v.Element())
|
||||
}
|
||||
return el
|
||||
}
|
||||
|
||||
// Attribute represents the SAML element Attribute.
|
||||
//
|
||||
// See http://docs.oasis-open.org/security/saml/v2.0/saml-core-2.0-os.pdf §2.7.3.1
|
||||
type Attribute struct {
|
||||
FriendlyName string `xml:",attr"`
|
||||
Name string `xml:",attr"`
|
||||
NameFormat string `xml:",attr"`
|
||||
Values []AttributeValue `xml:"AttributeValue"`
|
||||
}
|
||||
|
||||
// Element returns an etree.Element representing the object in XML form.
|
||||
func (a *Attribute) Element() *etree.Element {
|
||||
el := etree.NewElement("saml:Attribute")
|
||||
if a.FriendlyName != "" {
|
||||
el.CreateAttr("FriendlyName", a.FriendlyName)
|
||||
}
|
||||
if a.Name != "" {
|
||||
el.CreateAttr("Name", a.Name)
|
||||
}
|
||||
if a.NameFormat != "" {
|
||||
el.CreateAttr("NameFormat", a.NameFormat)
|
||||
}
|
||||
for _, v := range a.Values {
|
||||
el.AddChild(v.Element())
|
||||
}
|
||||
return el
|
||||
}
|
||||
|
||||
// AttributeValue represents the SAML element AttributeValue.
|
||||
//
|
||||
// See http://docs.oasis-open.org/security/saml/v2.0/saml-core-2.0-os.pdf §2.7.3.1.1
|
||||
type AttributeValue struct {
|
||||
Type string `xml:"http://www.w3.org/2001/XMLSchema-instance type,attr"`
|
||||
Value string `xml:",chardata"`
|
||||
NameID *NameID
|
||||
}
|
||||
|
||||
// Element returns an etree.Element representing the object in XML form.
|
||||
func (a *AttributeValue) Element() *etree.Element {
|
||||
el := etree.NewElement("saml:AttributeValue")
|
||||
el.CreateAttr("xmlns:xsi", "http://www.w3.org/2001/XMLSchema-instance")
|
||||
el.CreateAttr("xmlns:xs", "http://www.w3.org/2001/XMLSchema")
|
||||
el.CreateAttr("xsi:type", a.Type)
|
||||
if a.NameID != nil {
|
||||
el.AddChild(a.NameID.Element())
|
||||
}
|
||||
el.SetText(a.Value)
|
||||
return el
|
||||
}
|
683
vendor/github.com/crewjam/saml/service_provider.go
generated
vendored
Normal file
683
vendor/github.com/crewjam/saml/service_provider.go
generated
vendored
Normal file
@ -0,0 +1,683 @@
|
||||
package saml
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"compress/flate"
|
||||
"crypto/rsa"
|
||||
"crypto/x509"
|
||||
"encoding/base64"
|
||||
"encoding/xml"
|
||||
"errors"
|
||||
"fmt"
|
||||
"html/template"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"regexp"
|
||||
"time"
|
||||
|
||||
"github.com/beevik/etree"
|
||||
"github.com/crewjam/saml/logger"
|
||||
"github.com/crewjam/saml/xmlenc"
|
||||
dsig "github.com/russellhaering/goxmldsig"
|
||||
"github.com/russellhaering/goxmldsig/etreeutils"
|
||||
)
|
||||
|
||||
// NameIDFormat is the format of the id
|
||||
type NameIDFormat string
|
||||
|
||||
// Element returns an XML element representation of n.
|
||||
func (n NameIDFormat) Element() *etree.Element {
|
||||
el := etree.NewElement("")
|
||||
el.SetText(string(n))
|
||||
return el
|
||||
}
|
||||
|
||||
// Name ID formats
|
||||
const (
|
||||
UnspecifiedNameIDFormat NameIDFormat = "urn:oasis:names:tc:SAML:2.0:nameid-format:unspecified"
|
||||
TransientNameIDFormat NameIDFormat = "urn:oasis:names:tc:SAML:2.0:nameid-format:transient"
|
||||
EmailAddressNameIDFormat NameIDFormat = "urn:oasis:names:tc:SAML:2.0:nameid-format:emailAddress"
|
||||
PersistentNameIDFormat NameIDFormat = "urn:oasis:names:tc:SAML:2.0:nameid-format:persistent"
|
||||
)
|
||||
|
||||
// ServiceProvider implements SAML Service provider.
|
||||
//
|
||||
// In SAML, service providers delegate responsibility for identifying
|
||||
// clients to an identity provider. If you are writing an application
|
||||
// that uses passwords (or whatever) stored somewhere else, then you
|
||||
// are service provider.
|
||||
//
|
||||
// See the example directory for an example of a web application using
|
||||
// the service provider interface.
|
||||
type ServiceProvider struct {
|
||||
// Key is the RSA private key we use to sign requests.
|
||||
Key *rsa.PrivateKey
|
||||
|
||||
// Certificate is the RSA public part of Key.
|
||||
Certificate *x509.Certificate
|
||||
|
||||
// MetadataURL is the full URL to the metadata endpoint on this host,
|
||||
// i.e. https://example.com/saml/metadata
|
||||
MetadataURL url.URL
|
||||
|
||||
// AcsURL is the full URL to the SAML Assertion Customer Service endpoint
|
||||
// on this host, i.e. https://example.com/saml/acs
|
||||
AcsURL url.URL
|
||||
|
||||
// IDPMetadata is the metadata from the identity provider.
|
||||
IDPMetadata *EntityDescriptor
|
||||
|
||||
// AuthnNameIDFormat is the format used in the NameIDPolicy for
|
||||
// authentication requests
|
||||
AuthnNameIDFormat NameIDFormat
|
||||
|
||||
// MetadataValidDuration is a duration used to calculate validUntil
|
||||
// attribute in the metadata endpoint
|
||||
MetadataValidDuration time.Duration
|
||||
|
||||
// Logger is used to log messages for example in the event of errors
|
||||
Logger logger.Interface
|
||||
|
||||
// ForceAuthn allows you to force re-authentication of users even if the user
|
||||
// has a SSO session at the IdP.
|
||||
ForceAuthn *bool
|
||||
}
|
||||
|
||||
// MaxIssueDelay is the longest allowed time between when a SAML assertion is
|
||||
// issued by the IDP and the time it is received by ParseResponse. This is used
|
||||
// to prevent old responses from being replayed (while allowing for some clock
|
||||
// drift between the SP and IDP).
|
||||
var MaxIssueDelay = time.Second * 90
|
||||
|
||||
// MaxClockSkew allows for leeway for clock skew between the IDP and SP when
|
||||
// validating assertions. It defaults to 180 seconds (matches shibboleth).
|
||||
var MaxClockSkew = time.Second * 180
|
||||
|
||||
// DefaultValidDuration is how long we assert that the SP metadata is valid.
|
||||
const DefaultValidDuration = time.Hour * 24 * 2
|
||||
|
||||
// DefaultCacheDuration is how long we ask the IDP to cache the SP metadata.
|
||||
const DefaultCacheDuration = time.Hour * 24 * 1
|
||||
|
||||
// Metadata returns the service provider metadata
|
||||
func (sp *ServiceProvider) Metadata() *EntityDescriptor {
|
||||
validDuration := DefaultValidDuration
|
||||
if sp.MetadataValidDuration > 0 {
|
||||
validDuration = sp.MetadataValidDuration
|
||||
}
|
||||
|
||||
authnRequestsSigned := false
|
||||
wantAssertionsSigned := true
|
||||
validUntil := TimeNow().Add(validDuration)
|
||||
return &EntityDescriptor{
|
||||
EntityID: sp.MetadataURL.String(),
|
||||
ValidUntil: validUntil,
|
||||
|
||||
SPSSODescriptors: []SPSSODescriptor{
|
||||
{
|
||||
SSODescriptor: SSODescriptor{
|
||||
RoleDescriptor: RoleDescriptor{
|
||||
ProtocolSupportEnumeration: "urn:oasis:names:tc:SAML:2.0:protocol",
|
||||
KeyDescriptors: []KeyDescriptor{
|
||||
{
|
||||
Use: "signing",
|
||||
KeyInfo: KeyInfo{
|
||||
Certificate: base64.StdEncoding.EncodeToString(sp.Certificate.Raw),
|
||||
},
|
||||
},
|
||||
{
|
||||
Use: "encryption",
|
||||
KeyInfo: KeyInfo{
|
||||
Certificate: base64.StdEncoding.EncodeToString(sp.Certificate.Raw),
|
||||
},
|
||||
EncryptionMethods: []EncryptionMethod{
|
||||
{Algorithm: "http://www.w3.org/2001/04/xmlenc#aes128-cbc"},
|
||||
{Algorithm: "http://www.w3.org/2001/04/xmlenc#aes192-cbc"},
|
||||
{Algorithm: "http://www.w3.org/2001/04/xmlenc#aes256-cbc"},
|
||||
{Algorithm: "http://www.w3.org/2001/04/xmlenc#rsa-oaep-mgf1p"},
|
||||
},
|
||||
},
|
||||
},
|
||||
ValidUntil: validUntil,
|
||||
},
|
||||
},
|
||||
AuthnRequestsSigned: &authnRequestsSigned,
|
||||
WantAssertionsSigned: &wantAssertionsSigned,
|
||||
|
||||
AssertionConsumerServices: []IndexedEndpoint{
|
||||
{
|
||||
Binding: HTTPPostBinding,
|
||||
Location: sp.AcsURL.String(),
|
||||
Index: 1,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
// MakeRedirectAuthenticationRequest creates a SAML authentication request using
|
||||
// the HTTP-Redirect binding. It returns a URL that we will redirect the user to
|
||||
// in order to start the auth process.
|
||||
func (sp *ServiceProvider) MakeRedirectAuthenticationRequest(relayState string) (*url.URL, error) {
|
||||
req, err := sp.MakeAuthenticationRequest(sp.GetSSOBindingLocation(HTTPRedirectBinding))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return req.Redirect(relayState), nil
|
||||
}
|
||||
|
||||
// Redirect returns a URL suitable for using the redirect binding with the request
|
||||
func (req *AuthnRequest) 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())
|
||||
if _, err := doc.WriteTo(w2); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
w2.Close()
|
||||
w1.Close()
|
||||
|
||||
rv, _ := url.Parse(req.Destination)
|
||||
|
||||
query := rv.Query()
|
||||
query.Set("SAMLRequest", string(w.Bytes()))
|
||||
if relayState != "" {
|
||||
query.Set("RelayState", relayState)
|
||||
}
|
||||
rv.RawQuery = query.Encode()
|
||||
|
||||
return rv
|
||||
}
|
||||
|
||||
// GetSSOBindingLocation returns URL for the IDP's Single Sign On Service binding
|
||||
// of the specified type (HTTPRedirectBinding or HTTPPostBinding)
|
||||
func (sp *ServiceProvider) GetSSOBindingLocation(binding string) string {
|
||||
for _, idpSSODescriptor := range sp.IDPMetadata.IDPSSODescriptors {
|
||||
for _, singleSignOnService := range idpSSODescriptor.SingleSignOnServices {
|
||||
if singleSignOnService.Binding == binding {
|
||||
return singleSignOnService.Location
|
||||
}
|
||||
}
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
// getIDPSigningCerts returns the certificates which we can use to verify things
|
||||
// signed by the IDP in PEM format, or nil if no such certificate is found.
|
||||
func (sp *ServiceProvider) getIDPSigningCerts() ([]*x509.Certificate, error) {
|
||||
var certStrs []string
|
||||
for _, idpSSODescriptor := range sp.IDPMetadata.IDPSSODescriptors {
|
||||
for _, keyDescriptor := range idpSSODescriptor.KeyDescriptors {
|
||||
if keyDescriptor.Use == "signing" {
|
||||
certStrs = append(certStrs, keyDescriptor.KeyInfo.Certificate)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// If there are no explicitly signing certs, just return the first
|
||||
// non-empty cert we find.
|
||||
if len(certStrs) == 0 {
|
||||
for _, idpSSODescriptor := range sp.IDPMetadata.IDPSSODescriptors {
|
||||
for _, keyDescriptor := range idpSSODescriptor.KeyDescriptors {
|
||||
if keyDescriptor.Use == "" && keyDescriptor.KeyInfo.Certificate != "" {
|
||||
certStrs = append(certStrs, keyDescriptor.KeyInfo.Certificate)
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if len(certStrs) == 0 {
|
||||
return nil, errors.New("cannot find any signing certificate in the IDP SSO descriptor")
|
||||
}
|
||||
|
||||
var certs []*x509.Certificate
|
||||
|
||||
// cleanup whitespace
|
||||
regex := regexp.MustCompile(`\s+`)
|
||||
for _, certStr := range certStrs {
|
||||
certStr = regex.ReplaceAllString(certStr, "")
|
||||
certBytes, err := base64.StdEncoding.DecodeString(certStr)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("cannot parse certificate: %s", err)
|
||||
}
|
||||
|
||||
parsedCert, err := x509.ParseCertificate(certBytes)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
certs = append(certs, parsedCert)
|
||||
}
|
||||
|
||||
return certs, nil
|
||||
}
|
||||
|
||||
// MakeAuthenticationRequest produces a new AuthnRequest object for idpURL.
|
||||
func (sp *ServiceProvider) MakeAuthenticationRequest(idpURL string) (*AuthnRequest, error) {
|
||||
var nameIDFormat string
|
||||
switch sp.AuthnNameIDFormat {
|
||||
case "":
|
||||
// To maintain library back-compat, use "transient" if unset.
|
||||
nameIDFormat = string(TransientNameIDFormat)
|
||||
case UnspecifiedNameIDFormat:
|
||||
// Spec defines an empty value as "unspecified" so don't set one.
|
||||
default:
|
||||
nameIDFormat = string(sp.AuthnNameIDFormat)
|
||||
}
|
||||
|
||||
allowCreate := true
|
||||
req := AuthnRequest{
|
||||
AssertionConsumerServiceURL: sp.AcsURL.String(),
|
||||
Destination: idpURL,
|
||||
ProtocolBinding: HTTPPostBinding, // default binding for the response
|
||||
ID: fmt.Sprintf("id-%x", randomBytes(20)),
|
||||
IssueInstant: TimeNow(),
|
||||
Version: "2.0",
|
||||
Issuer: &Issuer{
|
||||
Format: "urn:oasis:names:tc:SAML:2.0:nameid-format:entity",
|
||||
Value: sp.MetadataURL.String(),
|
||||
},
|
||||
NameIDPolicy: &NameIDPolicy{
|
||||
AllowCreate: &allowCreate,
|
||||
// TODO(ross): figure out exactly policy we need
|
||||
// urn:mace:shibboleth:1.0:nameIdentifier
|
||||
// urn:oasis:names:tc:SAML:2.0:nameid-format:transient
|
||||
Format: &nameIDFormat,
|
||||
},
|
||||
ForceAuthn: sp.ForceAuthn,
|
||||
}
|
||||
return &req, nil
|
||||
}
|
||||
|
||||
// MakePostAuthenticationRequest creates a SAML authentication request using
|
||||
// the HTTP-POST binding. It returns HTML text representing an HTML form that
|
||||
// can be sent presented to a browser to initiate the login process.
|
||||
func (sp *ServiceProvider) MakePostAuthenticationRequest(relayState string) ([]byte, error) {
|
||||
req, err := sp.MakeAuthenticationRequest(sp.GetSSOBindingLocation(HTTPPostBinding))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return req.Post(relayState), nil
|
||||
}
|
||||
|
||||
// Post returns an HTML form suitable for using the HTTP-POST binding with the request
|
||||
func (req *AuthnRequest) Post(relayState string) []byte {
|
||||
doc := etree.NewDocument()
|
||||
doc.SetRoot(req.Element())
|
||||
reqBuf, err := doc.WriteToBytes()
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
encodedReqBuf := base64.StdEncoding.EncodeToString(reqBuf)
|
||||
|
||||
tmpl := template.Must(template.New("saml-post-form").Parse(`` +
|
||||
`<form method="post" action="{{.URL}}" id="SAMLRequestForm">` +
|
||||
`<input type="hidden" name="SAMLRequest" value="{{.SAMLRequest}}" />` +
|
||||
`<input type="hidden" name="RelayState" value="{{.RelayState}}" />` +
|
||||
`<input id="SAMLSubmitButton" type="submit" value="Submit" />` +
|
||||
`</form>` +
|
||||
`<script>document.getElementById('SAMLSubmitButton').style.visibility="hidden";` +
|
||||
`document.getElementById('SAMLRequestForm').submit();</script>`))
|
||||
data := struct {
|
||||
URL string
|
||||
SAMLRequest string
|
||||
RelayState string
|
||||
}{
|
||||
URL: req.Destination,
|
||||
SAMLRequest: encodedReqBuf,
|
||||
RelayState: relayState,
|
||||
}
|
||||
|
||||
rv := bytes.Buffer{}
|
||||
if err := tmpl.Execute(&rv, data); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
return rv.Bytes()
|
||||
}
|
||||
|
||||
// AssertionAttributes is a list of AssertionAttribute
|
||||
type AssertionAttributes []AssertionAttribute
|
||||
|
||||
// Get returns the assertion attribute whose Name or FriendlyName
|
||||
// matches name, or nil if no matching attribute is found.
|
||||
func (aa AssertionAttributes) Get(name string) *AssertionAttribute {
|
||||
for _, attr := range aa {
|
||||
if attr.Name == name {
|
||||
return &attr
|
||||
}
|
||||
if attr.FriendlyName == name {
|
||||
return &attr
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// AssertionAttribute represents an attribute of the user extracted from
|
||||
// a SAML Assertion.
|
||||
type AssertionAttribute struct {
|
||||
FriendlyName string
|
||||
Name string
|
||||
Value string
|
||||
}
|
||||
|
||||
// InvalidResponseError is the error produced by ParseResponse when it fails.
|
||||
// The underlying error is in PrivateErr. Response is the response as it was
|
||||
// known at the time validation failed. Now is the time that was used to validate
|
||||
// time-dependent parts of the assertion.
|
||||
type InvalidResponseError struct {
|
||||
PrivateErr error
|
||||
Response string
|
||||
Now time.Time
|
||||
}
|
||||
|
||||
func (ivr *InvalidResponseError) Error() string {
|
||||
return fmt.Sprintf("Authentication failed")
|
||||
}
|
||||
|
||||
// ParseResponse extracts the SAML IDP response received in req, validates
|
||||
// it, and returns the verified attributes of the request.
|
||||
//
|
||||
// This function handles decrypting the message, verifying the digital
|
||||
// signature on the assertion, and verifying that the specified conditions
|
||||
// and properties are met.
|
||||
//
|
||||
// If the function fails it will return an InvalidResponseError whose
|
||||
// properties are useful in describing which part of the parsing process
|
||||
// failed. However, to discourage inadvertent disclosure the diagnostic
|
||||
// information, the Error() method returns a static string.
|
||||
func (sp *ServiceProvider) ParseResponse(req *http.Request, possibleRequestIDs []string) (*Assertion, error) {
|
||||
now := TimeNow()
|
||||
retErr := &InvalidResponseError{
|
||||
Now: now,
|
||||
Response: req.PostForm.Get("SAMLResponse"),
|
||||
}
|
||||
|
||||
rawResponseBuf, err := base64.StdEncoding.DecodeString(req.PostForm.Get("SAMLResponse"))
|
||||
if err != nil {
|
||||
retErr.PrivateErr = fmt.Errorf("cannot parse base64: %s", err)
|
||||
return nil, retErr
|
||||
}
|
||||
retErr.Response = string(rawResponseBuf)
|
||||
|
||||
// do some validation first before we decrypt
|
||||
resp := Response{}
|
||||
if err := xml.Unmarshal(rawResponseBuf, &resp); err != nil {
|
||||
retErr.PrivateErr = fmt.Errorf("cannot unmarshal response: %s", err)
|
||||
return nil, retErr
|
||||
}
|
||||
if resp.Destination != sp.AcsURL.String() {
|
||||
retErr.PrivateErr = fmt.Errorf("`Destination` does not match AcsURL (expected %q)", sp.AcsURL.String())
|
||||
return nil, retErr
|
||||
}
|
||||
|
||||
requestIDvalid := false
|
||||
for _, possibleRequestID := range possibleRequestIDs {
|
||||
if resp.InResponseTo == possibleRequestID {
|
||||
requestIDvalid = true
|
||||
}
|
||||
}
|
||||
if !requestIDvalid {
|
||||
retErr.PrivateErr = fmt.Errorf("`InResponseTo` does not match any of the possible request IDs (expected %v)", possibleRequestIDs)
|
||||
return nil, retErr
|
||||
}
|
||||
|
||||
if resp.IssueInstant.Add(MaxIssueDelay).Before(now) {
|
||||
retErr.PrivateErr = fmt.Errorf("IssueInstant expired at %s", resp.IssueInstant.Add(MaxIssueDelay))
|
||||
return nil, retErr
|
||||
}
|
||||
if resp.Issuer.Value != sp.IDPMetadata.EntityID {
|
||||
retErr.PrivateErr = fmt.Errorf("Issuer does not match the IDP metadata (expected %q)", sp.IDPMetadata.EntityID)
|
||||
return nil, retErr
|
||||
}
|
||||
if resp.Status.StatusCode.Value != StatusSuccess {
|
||||
retErr.PrivateErr = fmt.Errorf("Status code was not %s", StatusSuccess)
|
||||
return nil, retErr
|
||||
}
|
||||
|
||||
var assertion *Assertion
|
||||
if resp.EncryptedAssertion == nil {
|
||||
|
||||
doc := etree.NewDocument()
|
||||
if err := doc.ReadFromBytes(rawResponseBuf); err != nil {
|
||||
retErr.PrivateErr = err
|
||||
return nil, retErr
|
||||
}
|
||||
|
||||
// TODO(ross): verify that the namespace is urn:oasis:names:tc:SAML:2.0:protocol
|
||||
responseEl := doc.Root()
|
||||
if responseEl.Tag != "Response" {
|
||||
retErr.PrivateErr = fmt.Errorf("expected to find a response object, not %s", doc.Root().Tag)
|
||||
return nil, retErr
|
||||
}
|
||||
|
||||
if err = sp.validateSigned(responseEl); err != nil {
|
||||
retErr.PrivateErr = err
|
||||
return nil, retErr
|
||||
}
|
||||
|
||||
assertion = resp.Assertion
|
||||
}
|
||||
|
||||
// decrypt the response
|
||||
if resp.EncryptedAssertion != nil {
|
||||
doc := etree.NewDocument()
|
||||
if err := doc.ReadFromBytes(rawResponseBuf); err != nil {
|
||||
retErr.PrivateErr = err
|
||||
return nil, retErr
|
||||
}
|
||||
var key interface{} = sp.Key
|
||||
keyEl := doc.FindElement("//EncryptedAssertion/EncryptedKey")
|
||||
if keyEl != nil {
|
||||
key, err = xmlenc.Decrypt(sp.Key, keyEl)
|
||||
if err != nil {
|
||||
retErr.PrivateErr = fmt.Errorf("failed to decrypt key from response: %s", err)
|
||||
return nil, retErr
|
||||
}
|
||||
}
|
||||
|
||||
el := doc.FindElement("//EncryptedAssertion/EncryptedData")
|
||||
plaintextAssertion, err := xmlenc.Decrypt(key, el)
|
||||
if err != nil {
|
||||
retErr.PrivateErr = fmt.Errorf("failed to decrypt response: %s", err)
|
||||
return nil, retErr
|
||||
}
|
||||
retErr.Response = string(plaintextAssertion)
|
||||
|
||||
doc = etree.NewDocument()
|
||||
if err := doc.ReadFromBytes(plaintextAssertion); err != nil {
|
||||
retErr.PrivateErr = fmt.Errorf("cannot parse plaintext response %v", err)
|
||||
return nil, retErr
|
||||
}
|
||||
|
||||
if err := sp.validateSigned(doc.Root()); err != nil {
|
||||
retErr.PrivateErr = err
|
||||
return nil, retErr
|
||||
}
|
||||
|
||||
assertion = &Assertion{}
|
||||
if err := xml.Unmarshal(plaintextAssertion, assertion); err != nil {
|
||||
retErr.PrivateErr = err
|
||||
return nil, retErr
|
||||
}
|
||||
}
|
||||
|
||||
if err := sp.validateAssertion(assertion, possibleRequestIDs, now); err != nil {
|
||||
retErr.PrivateErr = fmt.Errorf("assertion invalid: %s", err)
|
||||
return nil, retErr
|
||||
}
|
||||
|
||||
return assertion, nil
|
||||
}
|
||||
|
||||
// validateAssertion checks that the conditions specified in assertion match
|
||||
// the requirements to accept. If validation fails, it returns an error describing
|
||||
// the failure. (The digital signature on the assertion is not checked -- this
|
||||
// should be done before calling this function).
|
||||
func (sp *ServiceProvider) validateAssertion(assertion *Assertion, possibleRequestIDs []string, now time.Time) error {
|
||||
if assertion.IssueInstant.Add(MaxIssueDelay).Before(now) {
|
||||
return fmt.Errorf("expired on %s", assertion.IssueInstant.Add(MaxIssueDelay))
|
||||
}
|
||||
if assertion.Issuer.Value != sp.IDPMetadata.EntityID {
|
||||
return fmt.Errorf("issuer is not %q", sp.IDPMetadata.EntityID)
|
||||
}
|
||||
for _, subjectConfirmation := range assertion.Subject.SubjectConfirmations {
|
||||
requestIDvalid := false
|
||||
for _, possibleRequestID := range possibleRequestIDs {
|
||||
if subjectConfirmation.SubjectConfirmationData.InResponseTo == possibleRequestID {
|
||||
requestIDvalid = true
|
||||
break
|
||||
}
|
||||
}
|
||||
if !requestIDvalid {
|
||||
return fmt.Errorf("SubjectConfirmation one of the possible request IDs (%v)", possibleRequestIDs)
|
||||
}
|
||||
if subjectConfirmation.SubjectConfirmationData.Recipient != sp.AcsURL.String() {
|
||||
return fmt.Errorf("SubjectConfirmation Recipient is not %s", sp.AcsURL.String())
|
||||
}
|
||||
if subjectConfirmation.SubjectConfirmationData.NotOnOrAfter.Add(MaxClockSkew).Before(now) {
|
||||
return fmt.Errorf("SubjectConfirmationData is expired")
|
||||
}
|
||||
}
|
||||
if assertion.Conditions.NotBefore.Add(-MaxClockSkew).After(now) {
|
||||
return fmt.Errorf("Conditions is not yet valid")
|
||||
}
|
||||
if assertion.Conditions.NotOnOrAfter.Add(MaxClockSkew).Before(now) {
|
||||
return fmt.Errorf("Conditions is expired")
|
||||
}
|
||||
|
||||
audienceRestrictionsValid := len(assertion.Conditions.AudienceRestrictions) == 0
|
||||
for _, audienceRestriction := range assertion.Conditions.AudienceRestrictions {
|
||||
if audienceRestriction.Audience.Value == sp.MetadataURL.String() {
|
||||
audienceRestrictionsValid = true
|
||||
}
|
||||
}
|
||||
if !audienceRestrictionsValid {
|
||||
return fmt.Errorf("Conditions AudienceRestriction does not contain %q", sp.MetadataURL.String())
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func findChild(parentEl *etree.Element, childNS string, childTag string) (*etree.Element, error) {
|
||||
for _, childEl := range parentEl.ChildElements() {
|
||||
if childEl.Tag != childTag {
|
||||
continue
|
||||
}
|
||||
|
||||
ctx, err := etreeutils.NSBuildParentContext(childEl)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
ctx, err = ctx.SubContext(childEl)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
ns, err := ctx.LookupPrefix(childEl.Space)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("[%s]:%s cannot find prefix %s: %v", childNS, childTag, childEl.Space, err)
|
||||
}
|
||||
if ns != childNS {
|
||||
continue
|
||||
}
|
||||
|
||||
return childEl, nil
|
||||
}
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
// validateSigned returns a nil error iff each of the signatures on the Response and Assertion elements
|
||||
// are valid and there is at least one signature.
|
||||
func (sp *ServiceProvider) validateSigned(responseEl *etree.Element) error {
|
||||
haveSignature := false
|
||||
|
||||
// Some SAML responses have the signature on the Response object, and some on the Assertion
|
||||
// object, and some on both. We will require that at least one signature be present and that
|
||||
// all signatures be valid
|
||||
sigEl, err := findChild(responseEl, "http://www.w3.org/2000/09/xmldsig#", "Signature")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if sigEl != nil {
|
||||
if err = sp.validateSignature(responseEl); err != nil {
|
||||
return fmt.Errorf("cannot validate signature on Response: %v", err)
|
||||
}
|
||||
haveSignature = true
|
||||
}
|
||||
|
||||
assertionEl, err := findChild(responseEl, "urn:oasis:names:tc:SAML:2.0:assertion", "Assertion")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if assertionEl != nil {
|
||||
sigEl, err := findChild(assertionEl, "http://www.w3.org/2000/09/xmldsig#", "Signature")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if sigEl != nil {
|
||||
if err = sp.validateSignature(assertionEl); err != nil {
|
||||
return fmt.Errorf("cannot validate signature on Response: %v", err)
|
||||
}
|
||||
haveSignature = true
|
||||
}
|
||||
}
|
||||
|
||||
if !haveSignature {
|
||||
return errors.New("either the Response or Assertion must be signed")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// validateSignature returns nill iff the Signature embedded in the element is valid
|
||||
func (sp *ServiceProvider) validateSignature(el *etree.Element) error {
|
||||
certs, err := sp.getIDPSigningCerts()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
certificateStore := dsig.MemoryX509CertificateStore{
|
||||
Roots: certs,
|
||||
}
|
||||
|
||||
validationContext := dsig.NewDefaultValidationContext(&certificateStore)
|
||||
validationContext.IdAttribute = "ID"
|
||||
if Clock != nil {
|
||||
validationContext.Clock = Clock
|
||||
}
|
||||
|
||||
// Some SAML responses contain a RSAKeyValue element. One of two things is happening here:
|
||||
//
|
||||
// (1) We're getting something signed by a key we already know about -- the public key
|
||||
// of the signing cert provided in the metadata.
|
||||
// (2) We're getting something signed by a key we *don't* know about, and which we have
|
||||
// no ability to verify.
|
||||
//
|
||||
// The best course of action is to just remove the KeyInfo so that dsig falls back to
|
||||
// verifying against the public key provided in the metadata.
|
||||
if el.FindElement("./Signature/KeyInfo/X509Data/X509Certificate") == nil {
|
||||
if sigEl := el.FindElement("./Signature"); sigEl != nil {
|
||||
if keyInfo := sigEl.FindElement("KeyInfo"); keyInfo != nil {
|
||||
sigEl.RemoveChild(keyInfo)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ctx, err := etreeutils.NSBuildParentContext(el)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
ctx, err = ctx.SubContext(el)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
el, err = etreeutils.NSDetatch(ctx, el)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
_, err = validationContext.Validate(el)
|
||||
return err
|
||||
}
|
52
vendor/github.com/crewjam/saml/time.go
generated
vendored
Normal file
52
vendor/github.com/crewjam/saml/time.go
generated
vendored
Normal file
@ -0,0 +1,52 @@
|
||||
package saml
|
||||
|
||||
import "time"
|
||||
|
||||
// RelaxedTime is a version of time.Time that supports the time format
|
||||
// found in SAML documents.
|
||||
type RelaxedTime time.Time
|
||||
|
||||
const timeFormat = "2006-01-02T15:04:05.999Z07:00"
|
||||
|
||||
// MarshalText implements encoding.TextMarshaler
|
||||
func (m RelaxedTime) MarshalText() ([]byte, error) {
|
||||
// According to section 1.2.2 of the OASIS SAML 1.1 spec, we can't trust
|
||||
// other applications to handle time resolution finer than a millisecond.
|
||||
//
|
||||
// The time MUST be expressed in UTC.
|
||||
return []byte(m.String()), nil
|
||||
}
|
||||
|
||||
func (m RelaxedTime) String() string {
|
||||
return time.Time(m).Round(time.Millisecond).UTC().Format(timeFormat)
|
||||
}
|
||||
|
||||
// UnmarshalText implements encoding.TextUnmarshaler
|
||||
func (m *RelaxedTime) UnmarshalText(text []byte) error {
|
||||
if len(text) == 0 {
|
||||
*m = RelaxedTime(time.Time{})
|
||||
return nil
|
||||
}
|
||||
t, err1 := time.Parse(time.RFC3339, string(text))
|
||||
if err1 == nil {
|
||||
t = t.Round(time.Millisecond)
|
||||
*m = RelaxedTime(t)
|
||||
return nil
|
||||
}
|
||||
|
||||
t, err2 := time.Parse(time.RFC3339Nano, string(text))
|
||||
if err2 == nil {
|
||||
t = t.Round(time.Millisecond)
|
||||
*m = RelaxedTime(t)
|
||||
return nil
|
||||
}
|
||||
|
||||
t, err2 = time.Parse("2006-01-02T15:04:05.999999999", string(text))
|
||||
if err2 == nil {
|
||||
t = t.Round(time.Millisecond)
|
||||
*m = RelaxedTime(t)
|
||||
return nil
|
||||
}
|
||||
|
||||
return err1
|
||||
}
|
29
vendor/github.com/crewjam/saml/util.go
generated
vendored
Normal file
29
vendor/github.com/crewjam/saml/util.go
generated
vendored
Normal file
@ -0,0 +1,29 @@
|
||||
package saml
|
||||
|
||||
import (
|
||||
"crypto/rand"
|
||||
"time"
|
||||
|
||||
dsig "github.com/russellhaering/goxmldsig"
|
||||
)
|
||||
|
||||
// TimeNow is a function that returns the current time. The default
|
||||
// value is time.Now, but it can be replaced for testing.
|
||||
var TimeNow = func() time.Time { return time.Now().UTC() }
|
||||
|
||||
// Clock is assigned to dsig validation and signing contexts if it is
|
||||
// not nil, otherwise the default clock is used.
|
||||
var Clock *dsig.Clock
|
||||
|
||||
// RandReader is the io.Reader that produces cryptographically random
|
||||
// bytes when they are need by the library. The default value is
|
||||
// rand.Reader, but it can be replaced for testing.
|
||||
var RandReader = rand.Reader
|
||||
|
||||
func randomBytes(n int) []byte {
|
||||
rv := make([]byte, n)
|
||||
if _, err := RandReader.Read(rv); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return rv
|
||||
}
|
187
vendor/github.com/crewjam/saml/xmlenc/cbc.go
generated
vendored
Normal file
187
vendor/github.com/crewjam/saml/xmlenc/cbc.go
generated
vendored
Normal file
@ -0,0 +1,187 @@
|
||||
package xmlenc
|
||||
|
||||
import (
|
||||
"crypto/aes"
|
||||
"crypto/cipher"
|
||||
"crypto/des" // nolint: gas
|
||||
"encoding/base64"
|
||||
"errors"
|
||||
"fmt"
|
||||
|
||||
"github.com/beevik/etree"
|
||||
)
|
||||
|
||||
// CBC implements Decrypter and Encrypter for block ciphers in CBC mode
|
||||
type CBC struct {
|
||||
keySize int
|
||||
algorithm string
|
||||
cipher func([]byte) (cipher.Block, error)
|
||||
}
|
||||
|
||||
// KeySize returns the length of the key required.
|
||||
func (e CBC) KeySize() int {
|
||||
return e.keySize
|
||||
}
|
||||
|
||||
// Algorithm returns the name of the algorithm, as will be found
|
||||
// in an xenc:EncryptionMethod element.
|
||||
func (e CBC) Algorithm() string {
|
||||
return e.algorithm
|
||||
}
|
||||
|
||||
// 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) (*etree.Element, error) {
|
||||
keyBuf, ok := key.([]byte)
|
||||
if !ok {
|
||||
return nil, ErrIncorrectKeyType("[]byte")
|
||||
}
|
||||
if len(keyBuf) != e.keySize {
|
||||
return nil, ErrIncorrectKeyLength(e.keySize)
|
||||
}
|
||||
|
||||
block, err := e.cipher(keyBuf)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
encryptedDataEl := etree.NewElement("xenc:EncryptedData")
|
||||
encryptedDataEl.CreateAttr("xmlns:xenc", "http://www.w3.org/2001/04/xmlenc#")
|
||||
{
|
||||
randBuf := make([]byte, 16)
|
||||
if _, err := RandReader.Read(randBuf); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
encryptedDataEl.CreateAttr("Id", fmt.Sprintf("_%x", randBuf))
|
||||
}
|
||||
|
||||
em := encryptedDataEl.CreateElement("xenc:EncryptionMethod")
|
||||
em.CreateAttr("Algorithm", e.algorithm)
|
||||
em.CreateAttr("xmlns:xenc", "http://www.w3.org/2001/04/xmlenc#")
|
||||
|
||||
plaintext = appendPadding(plaintext, block.BlockSize())
|
||||
|
||||
iv := make([]byte, block.BlockSize())
|
||||
if _, err := RandReader.Read(iv); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
mode := cipher.NewCBCEncrypter(block, iv)
|
||||
ciphertext := make([]byte, len(plaintext))
|
||||
mode.CryptBlocks(ciphertext, plaintext)
|
||||
ciphertext = append(iv, ciphertext...)
|
||||
|
||||
cd := encryptedDataEl.CreateElement("xenc:CipherData")
|
||||
cd.CreateAttr("xmlns:xenc", "http://www.w3.org/2001/04/xmlenc#")
|
||||
cd.CreateElement("xenc:CipherValue").SetText(base64.StdEncoding.EncodeToString(ciphertext))
|
||||
return encryptedDataEl, nil
|
||||
}
|
||||
|
||||
// Decrypt decrypts an encrypted element with key. If the ciphertext contains an
|
||||
// EncryptedKey element, then the type of `key` is determined by the registered
|
||||
// Decryptor for the EncryptedKey element. Otherwise, `key` must be a []byte of
|
||||
// length KeySize().
|
||||
func (e CBC) Decrypt(key interface{}, ciphertextEl *etree.Element) ([]byte, error) {
|
||||
// If the key is encrypted, decrypt it.
|
||||
if encryptedKeyEl := ciphertextEl.FindElement("./KeyInfo/EncryptedKey"); encryptedKeyEl != nil {
|
||||
var err error
|
||||
key, err = Decrypt(key, encryptedKeyEl)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
keyBuf, ok := key.([]byte)
|
||||
if !ok {
|
||||
return nil, ErrIncorrectKeyType("[]byte")
|
||||
}
|
||||
if len(keyBuf) != e.KeySize() {
|
||||
return nil, ErrIncorrectKeyLength(e.KeySize())
|
||||
}
|
||||
|
||||
block, err := e.cipher(keyBuf)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
ciphertext, err := getCiphertext(ciphertextEl)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if len(ciphertext) < block.BlockSize() {
|
||||
return nil, errors.New("ciphertext too short")
|
||||
}
|
||||
|
||||
iv := ciphertext[:aes.BlockSize]
|
||||
ciphertext = ciphertext[aes.BlockSize:]
|
||||
|
||||
mode := cipher.NewCBCDecrypter(block, iv)
|
||||
plaintext := make([]byte, len(ciphertext))
|
||||
mode.CryptBlocks(plaintext, ciphertext) // decrypt in place
|
||||
|
||||
plaintext, err = stripPadding(plaintext)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return plaintext, nil
|
||||
}
|
||||
|
||||
var (
|
||||
// AES128CBC implements AES128-CBC symetric key mode for encryption and decryption
|
||||
AES128CBC BlockCipher = CBC{
|
||||
keySize: 16,
|
||||
algorithm: "http://www.w3.org/2001/04/xmlenc#aes128-cbc",
|
||||
cipher: aes.NewCipher,
|
||||
}
|
||||
|
||||
// AES192CBC implements AES192-CBC symetric key mode for encryption and decryption
|
||||
AES192CBC BlockCipher = CBC{
|
||||
keySize: 24,
|
||||
algorithm: "http://www.w3.org/2001/04/xmlenc#aes192-cbc",
|
||||
cipher: aes.NewCipher,
|
||||
}
|
||||
|
||||
// AES256CBC implements AES256-CBC symetric key mode for encryption and decryption
|
||||
AES256CBC BlockCipher = CBC{
|
||||
keySize: 32,
|
||||
algorithm: "http://www.w3.org/2001/04/xmlenc#aes256-cbc",
|
||||
cipher: aes.NewCipher,
|
||||
}
|
||||
|
||||
// TripleDES implements 3DES in CBC mode for encryption and decryption
|
||||
TripleDES BlockCipher = CBC{
|
||||
keySize: 8,
|
||||
algorithm: "http://www.w3.org/2001/04/xmlenc#tripledes-cbc",
|
||||
cipher: des.NewCipher,
|
||||
}
|
||||
)
|
||||
|
||||
func init() {
|
||||
RegisterDecrypter(AES128CBC)
|
||||
RegisterDecrypter(AES192CBC)
|
||||
RegisterDecrypter(AES256CBC)
|
||||
RegisterDecrypter(TripleDES)
|
||||
}
|
||||
|
||||
func appendPadding(buf []byte, blockSize int) []byte {
|
||||
paddingBytes := blockSize - (len(buf) % blockSize)
|
||||
padding := make([]byte, paddingBytes)
|
||||
padding[len(padding)-1] = byte(paddingBytes)
|
||||
return append(buf, padding...)
|
||||
}
|
||||
|
||||
func stripPadding(buf []byte) ([]byte, error) {
|
||||
if len(buf) < 1 {
|
||||
return nil, errors.New("buffer is too short for padding")
|
||||
}
|
||||
paddingBytes := int(buf[len(buf)-1])
|
||||
if paddingBytes > len(buf)-1 {
|
||||
return nil, errors.New("buffer is too short for padding")
|
||||
}
|
||||
if paddingBytes < 1 {
|
||||
return nil, errors.New("padding must be at least one byte")
|
||||
}
|
||||
return buf[:len(buf)-paddingBytes], nil
|
||||
}
|
117
vendor/github.com/crewjam/saml/xmlenc/decrypt.go
generated
vendored
Normal file
117
vendor/github.com/crewjam/saml/xmlenc/decrypt.go
generated
vendored
Normal file
@ -0,0 +1,117 @@
|
||||
package xmlenc
|
||||
|
||||
import (
|
||||
|
||||
// nolint: gas
|
||||
"crypto/rsa"
|
||||
"crypto/x509"
|
||||
"encoding/base64"
|
||||
"encoding/pem"
|
||||
"errors"
|
||||
"fmt"
|
||||
|
||||
"strings"
|
||||
|
||||
"github.com/beevik/etree"
|
||||
)
|
||||
|
||||
// ErrAlgorithmNotImplemented is returned when encryption used is not
|
||||
// supported.
|
||||
type ErrAlgorithmNotImplemented string
|
||||
|
||||
func (e ErrAlgorithmNotImplemented) Error() string {
|
||||
return "algorithm is not implemented: " + string(e)
|
||||
}
|
||||
|
||||
// ErrCannotFindRequiredElement is returned by Decrypt when a required
|
||||
// element cannot be found.
|
||||
type ErrCannotFindRequiredElement string
|
||||
|
||||
func (e ErrCannotFindRequiredElement) Error() string {
|
||||
return "cannot find required element: " + string(e)
|
||||
}
|
||||
|
||||
// ErrIncorrectTag is returned when Decrypt is passed an element which
|
||||
// is neither an EncryptedType nor an EncryptedKey
|
||||
var ErrIncorrectTag = fmt.Errorf("tag must be an EncryptedType or EncryptedKey")
|
||||
|
||||
// ErrIncorrectKeyLength is returned when the fixed length key is not
|
||||
// of the required length.
|
||||
type ErrIncorrectKeyLength int
|
||||
|
||||
func (e ErrIncorrectKeyLength) Error() string {
|
||||
return fmt.Sprintf("expected key to be %d bytes", int(e))
|
||||
}
|
||||
|
||||
// ErrIncorrectKeyType is returned when the key is not the correct type
|
||||
type ErrIncorrectKeyType string
|
||||
|
||||
func (e ErrIncorrectKeyType) Error() string {
|
||||
return fmt.Sprintf("expected key to be %s", string(e))
|
||||
}
|
||||
|
||||
// Decrypt decrypts the encrypted data using the provided key. If the
|
||||
// data are encrypted using AES or 3DEC, then the key should be a []byte.
|
||||
// If the data are encrypted with PKCS1v15 or RSA-OAEP-MGF1P then key should
|
||||
// be a *rsa.PrivateKey.
|
||||
func Decrypt(key interface{}, ciphertextEl *etree.Element) ([]byte, error) {
|
||||
encryptionMethodEl := ciphertextEl.FindElement("./EncryptionMethod")
|
||||
if encryptionMethodEl == nil {
|
||||
return nil, ErrCannotFindRequiredElement("EncryptionMethod")
|
||||
}
|
||||
algorithm := encryptionMethodEl.SelectAttrValue("Algorithm", "")
|
||||
decrypter, ok := decrypters[algorithm]
|
||||
if !ok {
|
||||
return nil, ErrAlgorithmNotImplemented(algorithm)
|
||||
}
|
||||
return decrypter.Decrypt(key, ciphertextEl)
|
||||
}
|
||||
|
||||
func getCiphertext(encryptedKey *etree.Element) ([]byte, error) {
|
||||
ciphertextEl := encryptedKey.FindElement("./CipherData/CipherValue")
|
||||
if ciphertextEl == nil {
|
||||
return nil, fmt.Errorf("cannot find CipherData element containing a CipherValue element")
|
||||
}
|
||||
ciphertext, err := base64.StdEncoding.DecodeString(strings.TrimSpace(ciphertextEl.Text()))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return ciphertext, nil
|
||||
}
|
||||
|
||||
func validateRSAKey(key interface{}, encryptedKey *etree.Element) (*rsa.PrivateKey, error) {
|
||||
rsaKey, ok := key.(*rsa.PrivateKey)
|
||||
if !ok {
|
||||
return nil, errors.New("expected key to be a *rsa.PrivateKey")
|
||||
}
|
||||
|
||||
// extract and verify that the public key matches the certificate
|
||||
// this section is included to either let the service know up front
|
||||
// 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.
|
||||
if el := encryptedKey.FindElement("./KeyInfo/X509Data/X509Certificate"); el != nil {
|
||||
certPEMbuf := el.Text()
|
||||
certPEMbuf = "-----BEGIN CERTIFICATE-----\n" + certPEMbuf + "\n-----END CERTIFICATE-----\n"
|
||||
certPEM, _ := pem.Decode([]byte(certPEMbuf))
|
||||
if certPEM == nil {
|
||||
return nil, fmt.Errorf("invalid certificate")
|
||||
}
|
||||
cert, err := x509.ParseCertificate(certPEM.Bytes)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
pubKey, ok := cert.PublicKey.(*rsa.PublicKey)
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("expected certificate to be an *rsa.PublicKey")
|
||||
}
|
||||
if rsaKey.N.Cmp(pubKey.N) != 0 || rsaKey.E != pubKey.E {
|
||||
return nil, fmt.Errorf("certificate does not match provided key")
|
||||
}
|
||||
} else if el = encryptedKey.FindElement("./KeyInfo/X509Data/X509IssuerSerial"); el != nil {
|
||||
// TODO: determine how to validate the issuer serial information
|
||||
} else {
|
||||
return nil, ErrCannotFindRequiredElement("X509Certificate or X509IssuerSerial")
|
||||
}
|
||||
return rsaKey, nil
|
||||
}
|
56
vendor/github.com/crewjam/saml/xmlenc/digest.go
generated
vendored
Normal file
56
vendor/github.com/crewjam/saml/xmlenc/digest.go
generated
vendored
Normal file
@ -0,0 +1,56 @@
|
||||
package xmlenc
|
||||
|
||||
import (
|
||||
"crypto/sha1"
|
||||
"crypto/sha256"
|
||||
"crypto/sha512"
|
||||
"hash"
|
||||
|
||||
"golang.org/x/crypto/ripemd160"
|
||||
)
|
||||
|
||||
type digestMethod struct {
|
||||
algorithm string
|
||||
hash func() hash.Hash
|
||||
}
|
||||
|
||||
func (dm digestMethod) Algorithm() string {
|
||||
return dm.algorithm
|
||||
}
|
||||
|
||||
func (dm digestMethod) Hash() hash.Hash {
|
||||
return dm.hash()
|
||||
}
|
||||
|
||||
var (
|
||||
// SHA1 implements the SHA-1 digest method (which is considered insecure)
|
||||
SHA1 = digestMethod{
|
||||
algorithm: "http://www.w3.org/2000/09/xmldsig#sha1",
|
||||
hash: sha1.New,
|
||||
}
|
||||
|
||||
// SHA256 implements the SHA-256 digest method
|
||||
SHA256 = digestMethod{
|
||||
algorithm: "http://www.w3.org/2000/09/xmldsig#sha256",
|
||||
hash: sha256.New,
|
||||
}
|
||||
|
||||
// SHA512 implements the SHA-512 digest method
|
||||
SHA512 = digestMethod{
|
||||
algorithm: "http://www.w3.org/2000/09/xmldsig#sha512",
|
||||
hash: sha512.New,
|
||||
}
|
||||
|
||||
// RIPEMD160 implements the RIPEMD160 digest method
|
||||
RIPEMD160 = digestMethod{
|
||||
algorithm: "http://www.w3.org/2000/09/xmldsig#ripemd160",
|
||||
hash: ripemd160.New,
|
||||
}
|
||||
)
|
||||
|
||||
func init() {
|
||||
RegisterDigestMethod(SHA1)
|
||||
RegisterDigestMethod(SHA256)
|
||||
RegisterDigestMethod(SHA512)
|
||||
RegisterDigestMethod(RIPEMD160)
|
||||
}
|
49
vendor/github.com/crewjam/saml/xmlenc/fuzz.go
generated
vendored
Normal file
49
vendor/github.com/crewjam/saml/xmlenc/fuzz.go
generated
vendored
Normal file
@ -0,0 +1,49 @@
|
||||
package xmlenc
|
||||
|
||||
import (
|
||||
"crypto/rsa"
|
||||
"crypto/x509"
|
||||
"encoding/pem"
|
||||
|
||||
"github.com/beevik/etree"
|
||||
)
|
||||
|
||||
var testKey = func() *rsa.PrivateKey {
|
||||
const keyStr = `-----BEGIN RSA PRIVATE KEY-----
|
||||
MIICXQIBAAKBgQDkXTUsWzRVpUHjbDpWCfYDfXmQ/q4LkaioZoTpu4ut1Q3eQC5t
|
||||
gD14agJhgT8yzeY5S/YNlwCyuVkjuFyoyTHFX2IOPpz7jnh4KnQ+B1IH9fY/+kmk
|
||||
zHJgxSUDJsdUMPgGpKt5hnEn7ziXAWXLc2udFbnHwhi9TXXwRHGi9wZ4YwIDAQAB
|
||||
AoGBALNTnlXeqRI4W61DZ+v4ln/XIIeD9xiOoWrcVrNU2zL+g41ryQmkEqFkXcpD
|
||||
vGUg2xFTXTz+v0WZ1y39sIW6uKFRYUfaNsF6iVfGAyx1VWK/jgtPnCWDQy26Eby0
|
||||
BqpbZRy1a6MLYVEG/5bvZE01CDV4XttpTrNX91WWcYGduJxBAkEA6ED1ZOqIzBpu
|
||||
c2KAo+bWmroCH8+cSDk0gVq6bnRB+EEhRCmo/VgvndWLxfexdGmDIOAIisB06N5a
|
||||
GzBSCaEY/QJBAPu2cNvuuBNLwrlxPCwOEpIHYT4gJq8UMtg6O6N+u++nYCGhK6uo
|
||||
VCmrKY+UewyNIcsLZF0jsNI2qJjiU1vQxN8CQQDfQJnigMQwlfO3/Ga1po6Buu2R
|
||||
0IpkroB3G1R8GkrTrR+iGv2zUdKrwHsUOC2fPlFrB4+OeMOomRw6aG9jjDStAkB1
|
||||
ztiZhuvuVAoKIv5HnDqC0CNqIUAZtzlozDB3f+xT6SFr+/Plfn4Nlod4JMVGhZNo
|
||||
ZaeOlBLBAEX+cAcVtOs/AkBicZOAPv84ABmFfyhXhYaAuacaJLq//jg+t+URUOg+
|
||||
XZS9naRmawEQxOkZQVoMeKgvu05+V4MniFqdQBINIkr5
|
||||
-----END RSA PRIVATE KEY-----`
|
||||
b, _ := pem.Decode([]byte(keyStr))
|
||||
k, err := x509.ParsePKCS1PrivateKey(b.Bytes)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return k
|
||||
}()
|
||||
|
||||
// Fuzz is the go-fuzz fuzzing function
|
||||
func Fuzz(data []byte) int {
|
||||
doc := etree.NewDocument()
|
||||
if err := doc.ReadFromBytes(data); err != nil {
|
||||
return 0
|
||||
}
|
||||
if doc.Root() == nil {
|
||||
return 0
|
||||
}
|
||||
|
||||
if _, err := Decrypt(testKey, doc.Root()); err != nil {
|
||||
return 0
|
||||
}
|
||||
return 1
|
||||
}
|
162
vendor/github.com/crewjam/saml/xmlenc/pubkey.go
generated
vendored
Normal file
162
vendor/github.com/crewjam/saml/xmlenc/pubkey.go
generated
vendored
Normal file
@ -0,0 +1,162 @@
|
||||
package xmlenc
|
||||
|
||||
import (
|
||||
"crypto/rsa"
|
||||
"crypto/x509"
|
||||
"encoding/base64"
|
||||
"fmt"
|
||||
|
||||
"github.com/beevik/etree"
|
||||
)
|
||||
|
||||
// RSA implements Encrypter and Decrypter using RSA public key encryption.
|
||||
//
|
||||
// Use function like OAEP(), or PKCS1v15() to get an instance of this type ready
|
||||
// to use.
|
||||
type RSA struct {
|
||||
BlockCipher BlockCipher
|
||||
DigestMethod DigestMethod // only for OAEP
|
||||
|
||||
algorithm string
|
||||
keyEncrypter func(e RSA, pubKey *rsa.PublicKey, plaintext []byte) ([]byte, error)
|
||||
keyDecrypter func(e RSA, privKey *rsa.PrivateKey, ciphertext []byte) ([]byte, error)
|
||||
}
|
||||
|
||||
// Algorithm returns the name of the algorithm
|
||||
func (e RSA) Algorithm() string {
|
||||
return e.algorithm
|
||||
}
|
||||
|
||||
// Encrypt implements encrypter. certificate must be a []byte containing the ASN.1 bytes
|
||||
// of certificate containing an RSA public key.
|
||||
func (e RSA) Encrypt(certificate interface{}, plaintext []byte) (*etree.Element, error) {
|
||||
cert, ok := certificate.(*x509.Certificate)
|
||||
if !ok {
|
||||
return nil, ErrIncorrectKeyType("*x.509 certificate")
|
||||
}
|
||||
|
||||
pubKey, ok := cert.PublicKey.(*rsa.PublicKey)
|
||||
if !ok {
|
||||
return nil, ErrIncorrectKeyType("x.509 certificate with an RSA public key")
|
||||
}
|
||||
|
||||
// generate a key
|
||||
key := make([]byte, e.BlockCipher.KeySize())
|
||||
if _, err := RandReader.Read(key); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
keyInfoEl := etree.NewElement("ds:KeyInfo")
|
||||
keyInfoEl.CreateAttr("xmlns:ds", "http://www.w3.org/2000/09/xmldsig#")
|
||||
|
||||
encryptedKey := keyInfoEl.CreateElement("xenc:EncryptedKey")
|
||||
{
|
||||
randBuf := make([]byte, 16)
|
||||
if _, err := RandReader.Read(randBuf); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
encryptedKey.CreateAttr("Id", fmt.Sprintf("_%x", randBuf))
|
||||
}
|
||||
encryptedKey.CreateAttr("xmlns:xenc", "http://www.w3.org/2001/04/xmlenc#")
|
||||
|
||||
encryptionMethodEl := encryptedKey.CreateElement("xenc:EncryptionMethod")
|
||||
encryptionMethodEl.CreateAttr("Algorithm", e.algorithm)
|
||||
encryptionMethodEl.CreateAttr("xmlns:xenc", "http://www.w3.org/2001/04/xmlenc#")
|
||||
if e.DigestMethod != nil {
|
||||
dm := encryptionMethodEl.CreateElement("ds:DigestMethod")
|
||||
dm.CreateAttr("Algorithm", e.DigestMethod.Algorithm())
|
||||
dm.CreateAttr("xmlns:ds", "http://www.w3.org/2000/09/xmldsig#")
|
||||
}
|
||||
{
|
||||
innerKeyInfoEl := encryptedKey.CreateElement("ds:KeyInfo")
|
||||
x509data := innerKeyInfoEl.CreateElement("ds:X509Data")
|
||||
x509data.CreateElement("ds:X509Certificate").SetText(
|
||||
base64.StdEncoding.EncodeToString(cert.Raw),
|
||||
)
|
||||
}
|
||||
|
||||
buf, err := e.keyEncrypter(e, pubKey, key)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
cd := encryptedKey.CreateElement("xenc:CipherData")
|
||||
cd.CreateAttr("xmlns:xenc", "http://www.w3.org/2001/04/xmlenc#")
|
||||
cd.CreateElement("xenc:CipherValue").SetText(base64.StdEncoding.EncodeToString(buf))
|
||||
encryptedDataEl, err := e.BlockCipher.Encrypt(key, plaintext)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
encryptedDataEl.InsertChild(encryptedDataEl.FindElement("./CipherData"), keyInfoEl)
|
||||
|
||||
return encryptedDataEl, nil
|
||||
}
|
||||
|
||||
// Decrypt implements Decryptor. `key` must be an *rsa.PrivateKey.
|
||||
func (e RSA) Decrypt(key interface{}, ciphertextEl *etree.Element) ([]byte, error) {
|
||||
rsaKey, err := validateRSAKey(key, ciphertextEl)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
ciphertext, err := getCiphertext(ciphertextEl)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
{
|
||||
digestMethodEl := ciphertextEl.FindElement("./EncryptionMethod/DigestMethod")
|
||||
if digestMethodEl == nil {
|
||||
e.DigestMethod = SHA1
|
||||
} else {
|
||||
hashAlgorithmStr := digestMethodEl.SelectAttrValue("Algorithm", "")
|
||||
digestMethod, ok := digestMethods[hashAlgorithmStr]
|
||||
if !ok {
|
||||
return nil, ErrAlgorithmNotImplemented(hashAlgorithmStr)
|
||||
}
|
||||
e.DigestMethod = digestMethod
|
||||
}
|
||||
}
|
||||
|
||||
return e.keyDecrypter(e, rsaKey, ciphertext)
|
||||
}
|
||||
|
||||
// OAEP returns a version of RSA that implements RSA in OAEP-MGF1P mode. By default
|
||||
// the block cipher used is AES-256 CBC and the digest method is SHA-256. You can
|
||||
// specify other ciphers and digest methods by assigning to BlockCipher or
|
||||
// DigestMethod.
|
||||
func OAEP() RSA {
|
||||
return RSA{
|
||||
BlockCipher: AES256CBC,
|
||||
DigestMethod: SHA256,
|
||||
algorithm: "http://www.w3.org/2001/04/xmlenc#rsa-oaep-mgf1p",
|
||||
keyEncrypter: func(e RSA, pubKey *rsa.PublicKey, plaintext []byte) ([]byte, error) {
|
||||
return rsa.EncryptOAEP(e.DigestMethod.Hash(), RandReader, pubKey, plaintext, nil)
|
||||
},
|
||||
keyDecrypter: func(e RSA, privKey *rsa.PrivateKey, ciphertext []byte) ([]byte, error) {
|
||||
return rsa.DecryptOAEP(e.DigestMethod.Hash(), RandReader, privKey, ciphertext, nil)
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
// PKCS1v15 returns a version of RSA that implements RSA in PKCS1v15 mode. By default
|
||||
// the block cipher used is AES-256 CBC. The DigestMethod field is ignored because PKCS1v15
|
||||
// does not use a digest function.
|
||||
func PKCS1v15() RSA {
|
||||
return RSA{
|
||||
BlockCipher: AES256CBC,
|
||||
DigestMethod: nil,
|
||||
algorithm: "http://www.w3.org/2001/04/xmlenc#rsa-1_5",
|
||||
keyEncrypter: func(e RSA, pubKey *rsa.PublicKey, plaintext []byte) ([]byte, error) {
|
||||
return rsa.EncryptPKCS1v15(RandReader, pubKey, plaintext)
|
||||
},
|
||||
keyDecrypter: func(e RSA, privKey *rsa.PrivateKey, ciphertext []byte) ([]byte, error) {
|
||||
return rsa.DecryptPKCS1v15(RandReader, privKey, ciphertext)
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func init() {
|
||||
RegisterDecrypter(OAEP())
|
||||
RegisterDecrypter(PKCS1v15())
|
||||
}
|
63
vendor/github.com/crewjam/saml/xmlenc/xmlenc.go
generated
vendored
Normal file
63
vendor/github.com/crewjam/saml/xmlenc/xmlenc.go
generated
vendored
Normal file
@ -0,0 +1,63 @@
|
||||
// Package xmlenc is a partial implementation of the xmlenc standard
|
||||
// as described in https://www.w3.org/TR/2002/REC-xmlenc-core-20021210/Overview.html.
|
||||
// The purpose of this implementation is to support encrypted SAML assertions.
|
||||
package xmlenc
|
||||
|
||||
import (
|
||||
"crypto/rand"
|
||||
"hash"
|
||||
"io"
|
||||
|
||||
"github.com/beevik/etree"
|
||||
)
|
||||
|
||||
// RandReader is a thunk that allows test to replace the source of randomness used by
|
||||
// this package. By default it is Reader from crypto/rand.
|
||||
var RandReader io.Reader = rand.Reader
|
||||
|
||||
// Encrypter is an interface that encrypts things. Given a plaintext it returns an
|
||||
// XML EncryptedData or EncryptedKey element. The required type of `key` varies
|
||||
// depending on the implementation.
|
||||
type Encrypter interface {
|
||||
Encrypt(key interface{}, plaintext []byte) (*etree.Element, error)
|
||||
}
|
||||
|
||||
// Decrypter is an interface that decrypts things. The Decrypt() method returns the
|
||||
// plaintext version of the EncryptedData or EncryptedKey element passed.
|
||||
//
|
||||
// You probably don't have to use this interface directly, instead you may call
|
||||
// Decrypt() and it will examine the element to determine which Decrypter to use.
|
||||
type Decrypter interface {
|
||||
Algorithm() string
|
||||
Decrypt(key interface{}, ciphertextEl *etree.Element) ([]byte, error)
|
||||
}
|
||||
|
||||
// DigestMethod represents a digest method such as SHA1, etc.
|
||||
type DigestMethod interface {
|
||||
Algorithm() string
|
||||
Hash() hash.Hash
|
||||
}
|
||||
|
||||
var (
|
||||
decrypters = map[string]Decrypter{}
|
||||
digestMethods = map[string]DigestMethod{}
|
||||
)
|
||||
|
||||
// RegisterDecrypter registers the specified decrypter to that it can be
|
||||
// used with Decrypt().
|
||||
func RegisterDecrypter(d Decrypter) {
|
||||
decrypters[d.Algorithm()] = d
|
||||
}
|
||||
|
||||
// RegisterDigestMethod registers the specified digest method to that it can be
|
||||
// used with Decrypt().
|
||||
func RegisterDigestMethod(dm DigestMethod) {
|
||||
digestMethods[dm.Algorithm()] = dm
|
||||
}
|
||||
|
||||
// BlockCipher implements a cipher with a fixed size key like AES or 3DES.
|
||||
type BlockCipher interface {
|
||||
Encrypter
|
||||
Decrypter
|
||||
KeySize() int
|
||||
}
|
25
vendor/github.com/jonboulle/clockwork/.gitignore
generated
vendored
Normal file
25
vendor/github.com/jonboulle/clockwork/.gitignore
generated
vendored
Normal file
@ -0,0 +1,25 @@
|
||||
# Compiled Object files, Static and Dynamic libs (Shared Objects)
|
||||
*.o
|
||||
*.a
|
||||
*.so
|
||||
|
||||
# Folders
|
||||
_obj
|
||||
_test
|
||||
|
||||
# Architecture specific extensions/prefixes
|
||||
*.[568vq]
|
||||
[568vq].out
|
||||
|
||||
*.cgo1.go
|
||||
*.cgo2.c
|
||||
_cgo_defun.c
|
||||
_cgo_gotypes.go
|
||||
_cgo_export.*
|
||||
|
||||
_testmain.go
|
||||
|
||||
*.exe
|
||||
*.test
|
||||
|
||||
*.swp
|
201
vendor/github.com/jonboulle/clockwork/LICENSE
generated
vendored
Normal file
201
vendor/github.com/jonboulle/clockwork/LICENSE
generated
vendored
Normal file
@ -0,0 +1,201 @@
|
||||
Apache License
|
||||
Version 2.0, January 2004
|
||||
http://www.apache.org/licenses/
|
||||
|
||||
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
||||
|
||||
1. Definitions.
|
||||
|
||||
"License" shall mean the terms and conditions for use, reproduction,
|
||||
and distribution as defined by Sections 1 through 9 of this document.
|
||||
|
||||
"Licensor" shall mean the copyright owner or entity authorized by
|
||||
the copyright owner that is granting the License.
|
||||
|
||||
"Legal Entity" shall mean the union of the acting entity and all
|
||||
other entities that control, are controlled by, or are under common
|
||||
control with that entity. For the purposes of this definition,
|
||||
"control" means (i) the power, direct or indirect, to cause the
|
||||
direction or management of such entity, whether by contract or
|
||||
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
||||
outstanding shares, or (iii) beneficial ownership of such entity.
|
||||
|
||||
"You" (or "Your") shall mean an individual or Legal Entity
|
||||
exercising permissions granted by this License.
|
||||
|
||||
"Source" form shall mean the preferred form for making modifications,
|
||||
including but not limited to software source code, documentation
|
||||
source, and configuration files.
|
||||
|
||||
"Object" form shall mean any form resulting from mechanical
|
||||
transformation or translation of a Source form, including but
|
||||
not limited to compiled object code, generated documentation,
|
||||
and conversions to other media types.
|
||||
|
||||
"Work" shall mean the work of authorship, whether in Source or
|
||||
Object form, made available under the License, as indicated by a
|
||||
copyright notice that is included in or attached to the work
|
||||
(an example is provided in the Appendix below).
|
||||
|
||||
"Derivative Works" shall mean any work, whether in Source or Object
|
||||
form, that is based on (or derived from) the Work and for which the
|
||||
editorial revisions, annotations, elaborations, or other modifications
|
||||
represent, as a whole, an original work of authorship. For the purposes
|
||||
of this License, Derivative Works shall not include works that remain
|
||||
separable from, or merely link (or bind by name) to the interfaces of,
|
||||
the Work and Derivative Works thereof.
|
||||
|
||||
"Contribution" shall mean any work of authorship, including
|
||||
the original version of the Work and any modifications or additions
|
||||
to that Work or Derivative Works thereof, that is intentionally
|
||||
submitted to Licensor for inclusion in the Work by the copyright owner
|
||||
or by an individual or Legal Entity authorized to submit on behalf of
|
||||
the copyright owner. For the purposes of this definition, "submitted"
|
||||
means any form of electronic, verbal, or written communication sent
|
||||
to the Licensor or its representatives, including but not limited to
|
||||
communication on electronic mailing lists, source code control systems,
|
||||
and issue tracking systems that are managed by, or on behalf of, the
|
||||
Licensor for the purpose of discussing and improving the Work, but
|
||||
excluding communication that is conspicuously marked or otherwise
|
||||
designated in writing by the copyright owner as "Not a Contribution."
|
||||
|
||||
"Contributor" shall mean Licensor and any individual or Legal Entity
|
||||
on behalf of whom a Contribution has been received by Licensor and
|
||||
subsequently incorporated within the Work.
|
||||
|
||||
2. Grant of Copyright License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
copyright license to reproduce, prepare Derivative Works of,
|
||||
publicly display, publicly perform, sublicense, and distribute the
|
||||
Work and such Derivative Works in Source or Object form.
|
||||
|
||||
3. Grant of Patent License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
(except as stated in this section) patent license to make, have made,
|
||||
use, offer to sell, sell, import, and otherwise transfer the Work,
|
||||
where such license applies only to those patent claims licensable
|
||||
by such Contributor that are necessarily infringed by their
|
||||
Contribution(s) alone or by combination of their Contribution(s)
|
||||
with the Work to which such Contribution(s) was submitted. If You
|
||||
institute patent litigation against any entity (including a
|
||||
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
||||
or a Contribution incorporated within the Work constitutes direct
|
||||
or contributory patent infringement, then any patent licenses
|
||||
granted to You under this License for that Work shall terminate
|
||||
as of the date such litigation is filed.
|
||||
|
||||
4. Redistribution. You may reproduce and distribute copies of the
|
||||
Work or Derivative Works thereof in any medium, with or without
|
||||
modifications, and in Source or Object form, provided that You
|
||||
meet the following conditions:
|
||||
|
||||
(a) You must give any other recipients of the Work or
|
||||
Derivative Works a copy of this License; and
|
||||
|
||||
(b) You must cause any modified files to carry prominent notices
|
||||
stating that You changed the files; and
|
||||
|
||||
(c) You must retain, in the Source form of any Derivative Works
|
||||
that You distribute, all copyright, patent, trademark, and
|
||||
attribution notices from the Source form of the Work,
|
||||
excluding those notices that do not pertain to any part of
|
||||
the Derivative Works; and
|
||||
|
||||
(d) If the Work includes a "NOTICE" text file as part of its
|
||||
distribution, then any Derivative Works that You distribute must
|
||||
include a readable copy of the attribution notices contained
|
||||
within such NOTICE file, excluding those notices that do not
|
||||
pertain to any part of the Derivative Works, in at least one
|
||||
of the following places: within a NOTICE text file distributed
|
||||
as part of the Derivative Works; within the Source form or
|
||||
documentation, if provided along with the Derivative Works; or,
|
||||
within a display generated by the Derivative Works, if and
|
||||
wherever such third-party notices normally appear. The contents
|
||||
of the NOTICE file are for informational purposes only and
|
||||
do not modify the License. You may add Your own attribution
|
||||
notices within Derivative Works that You distribute, alongside
|
||||
or as an addendum to the NOTICE text from the Work, provided
|
||||
that such additional attribution notices cannot be construed
|
||||
as modifying the License.
|
||||
|
||||
You may add Your own copyright statement to Your modifications and
|
||||
may provide additional or different license terms and conditions
|
||||
for use, reproduction, or distribution of Your modifications, or
|
||||
for any such Derivative Works as a whole, provided Your use,
|
||||
reproduction, and distribution of the Work otherwise complies with
|
||||
the conditions stated in this License.
|
||||
|
||||
5. Submission of Contributions. Unless You explicitly state otherwise,
|
||||
any Contribution intentionally submitted for inclusion in the Work
|
||||
by You to the Licensor shall be under the terms and conditions of
|
||||
this License, without any additional terms or conditions.
|
||||
Notwithstanding the above, nothing herein shall supersede or modify
|
||||
the terms of any separate license agreement you may have executed
|
||||
with Licensor regarding such Contributions.
|
||||
|
||||
6. Trademarks. This License does not grant permission to use the trade
|
||||
names, trademarks, service marks, or product names of the Licensor,
|
||||
except as required for reasonable and customary use in describing the
|
||||
origin of the Work and reproducing the content of the NOTICE file.
|
||||
|
||||
7. Disclaimer of Warranty. Unless required by applicable law or
|
||||
agreed to in writing, Licensor provides the Work (and each
|
||||
Contributor provides its Contributions) on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
||||
implied, including, without limitation, any warranties or conditions
|
||||
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
||||
PARTICULAR PURPOSE. You are solely responsible for determining the
|
||||
appropriateness of using or redistributing the Work and assume any
|
||||
risks associated with Your exercise of permissions under this License.
|
||||
|
||||
8. Limitation of Liability. In no event and under no legal theory,
|
||||
whether in tort (including negligence), contract, or otherwise,
|
||||
unless required by applicable law (such as deliberate and grossly
|
||||
negligent acts) or agreed to in writing, shall any Contributor be
|
||||
liable to You for damages, including any direct, indirect, special,
|
||||
incidental, or consequential damages of any character arising as a
|
||||
result of this License or out of the use or inability to use the
|
||||
Work (including but not limited to damages for loss of goodwill,
|
||||
work stoppage, computer failure or malfunction, or any and all
|
||||
other commercial damages or losses), even if such Contributor
|
||||
has been advised of the possibility of such damages.
|
||||
|
||||
9. Accepting Warranty or Additional Liability. While redistributing
|
||||
the Work or Derivative Works thereof, You may choose to offer,
|
||||
and charge a fee for, acceptance of support, warranty, indemnity,
|
||||
or other liability obligations and/or rights consistent with this
|
||||
License. However, in accepting such obligations, You may act only
|
||||
on Your own behalf and on Your sole responsibility, not on behalf
|
||||
of any other Contributor, and only if You agree to indemnify,
|
||||
defend, and hold each Contributor harmless for any liability
|
||||
incurred by, or claims asserted against, such Contributor by reason
|
||||
of your accepting any such warranty or additional liability.
|
||||
|
||||
END OF TERMS AND CONDITIONS
|
||||
|
||||
APPENDIX: How to apply the Apache License to your work.
|
||||
|
||||
To apply the Apache License to your work, attach the following
|
||||
boilerplate notice, with the fields enclosed by brackets "{}"
|
||||
replaced with your own identifying information. (Don't include
|
||||
the brackets!) The text should be enclosed in the appropriate
|
||||
comment syntax for the file format. We also recommend that a
|
||||
file or class name and description of purpose be included on the
|
||||
same "printed page" as the copyright notice for easier
|
||||
identification within third-party archives.
|
||||
|
||||
Copyright {yyyy} {name of copyright owner}
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
61
vendor/github.com/jonboulle/clockwork/README.md
generated
vendored
Normal file
61
vendor/github.com/jonboulle/clockwork/README.md
generated
vendored
Normal file
@ -0,0 +1,61 @@
|
||||
clockwork
|
||||
=========
|
||||
|
||||
[](https://travis-ci.org/jonboulle/clockwork)
|
||||
[](http://godoc.org/github.com/jonboulle/clockwork)
|
||||
|
||||
a simple fake clock for golang
|
||||
|
||||
# Usage
|
||||
|
||||
Replace uses of the `time` package with the `clockwork.Clock` interface instead.
|
||||
|
||||
For example, instead of using `time.Sleep` directly:
|
||||
|
||||
```
|
||||
func my_func() {
|
||||
time.Sleep(3 * time.Second)
|
||||
do_something()
|
||||
}
|
||||
```
|
||||
|
||||
inject a clock and use its `Sleep` method instead:
|
||||
|
||||
```
|
||||
func my_func(clock clockwork.Clock) {
|
||||
clock.Sleep(3 * time.Second)
|
||||
do_something()
|
||||
}
|
||||
```
|
||||
|
||||
Now you can easily test `my_func` with a `FakeClock`:
|
||||
|
||||
```
|
||||
func TestMyFunc(t *testing.T) {
|
||||
c := clockwork.NewFakeClock()
|
||||
|
||||
// Start our sleepy function
|
||||
my_func(c)
|
||||
|
||||
// Ensure we wait until my_func is sleeping
|
||||
c.BlockUntil(1)
|
||||
|
||||
assert_state()
|
||||
|
||||
// Advance the FakeClock forward in time
|
||||
c.Advance(3)
|
||||
|
||||
assert_state()
|
||||
}
|
||||
```
|
||||
|
||||
and in production builds, simply inject the real clock instead:
|
||||
```
|
||||
my_func(clockwork.NewRealClock())
|
||||
```
|
||||
|
||||
See [example_test.go](example_test.go) for a full example.
|
||||
|
||||
# Credits
|
||||
|
||||
clockwork is inspired by @wickman's [threaded fake clock](https://gist.github.com/wickman/3840816), and the [Golang playground](http://blog.golang.org/playground#Faking time)
|
169
vendor/github.com/jonboulle/clockwork/clockwork.go
generated
vendored
Normal file
169
vendor/github.com/jonboulle/clockwork/clockwork.go
generated
vendored
Normal file
@ -0,0 +1,169 @@
|
||||
package clockwork
|
||||
|
||||
import (
|
||||
"sync"
|
||||
"time"
|
||||
)
|
||||
|
||||
// Clock provides an interface that packages can use instead of directly
|
||||
// using the time module, so that chronology-related behavior can be tested
|
||||
type Clock interface {
|
||||
After(d time.Duration) <-chan time.Time
|
||||
Sleep(d time.Duration)
|
||||
Now() time.Time
|
||||
}
|
||||
|
||||
// FakeClock provides an interface for a clock which can be
|
||||
// manually advanced through time
|
||||
type FakeClock interface {
|
||||
Clock
|
||||
// Advance advances the FakeClock to a new point in time, ensuring any existing
|
||||
// sleepers are notified appropriately before returning
|
||||
Advance(d time.Duration)
|
||||
// BlockUntil will block until the FakeClock has the given number of
|
||||
// sleepers (callers of Sleep or After)
|
||||
BlockUntil(n int)
|
||||
}
|
||||
|
||||
// NewRealClock returns a Clock which simply delegates calls to the actual time
|
||||
// package; it should be used by packages in production.
|
||||
func NewRealClock() Clock {
|
||||
return &realClock{}
|
||||
}
|
||||
|
||||
// NewFakeClock returns a FakeClock implementation which can be
|
||||
// manually advanced through time for testing. The initial time of the
|
||||
// FakeClock will be an arbitrary non-zero time.
|
||||
func NewFakeClock() FakeClock {
|
||||
// use a fixture that does not fulfill Time.IsZero()
|
||||
return NewFakeClockAt(time.Date(1984, time.April, 4, 0, 0, 0, 0, time.UTC))
|
||||
}
|
||||
|
||||
// NewFakeClockAt returns a FakeClock initialised at the given time.Time.
|
||||
func NewFakeClockAt(t time.Time) FakeClock {
|
||||
return &fakeClock{
|
||||
time: t,
|
||||
}
|
||||
}
|
||||
|
||||
type realClock struct{}
|
||||
|
||||
func (rc *realClock) After(d time.Duration) <-chan time.Time {
|
||||
return time.After(d)
|
||||
}
|
||||
|
||||
func (rc *realClock) Sleep(d time.Duration) {
|
||||
time.Sleep(d)
|
||||
}
|
||||
|
||||
func (rc *realClock) Now() time.Time {
|
||||
return time.Now()
|
||||
}
|
||||
|
||||
type fakeClock struct {
|
||||
sleepers []*sleeper
|
||||
blockers []*blocker
|
||||
time time.Time
|
||||
|
||||
l sync.RWMutex
|
||||
}
|
||||
|
||||
// sleeper represents a caller of After or Sleep
|
||||
type sleeper struct {
|
||||
until time.Time
|
||||
done chan time.Time
|
||||
}
|
||||
|
||||
// blocker represents a caller of BlockUntil
|
||||
type blocker struct {
|
||||
count int
|
||||
ch chan struct{}
|
||||
}
|
||||
|
||||
// After mimics time.After; it waits for the given duration to elapse on the
|
||||
// fakeClock, then sends the current time on the returned channel.
|
||||
func (fc *fakeClock) After(d time.Duration) <-chan time.Time {
|
||||
fc.l.Lock()
|
||||
defer fc.l.Unlock()
|
||||
now := fc.time
|
||||
done := make(chan time.Time, 1)
|
||||
if d.Nanoseconds() == 0 {
|
||||
// special case - trigger immediately
|
||||
done <- now
|
||||
} else {
|
||||
// otherwise, add to the set of sleepers
|
||||
s := &sleeper{
|
||||
until: now.Add(d),
|
||||
done: done,
|
||||
}
|
||||
fc.sleepers = append(fc.sleepers, s)
|
||||
// and notify any blockers
|
||||
fc.blockers = notifyBlockers(fc.blockers, len(fc.sleepers))
|
||||
}
|
||||
return done
|
||||
}
|
||||
|
||||
// notifyBlockers notifies all the blockers waiting until the
|
||||
// given number of sleepers are waiting on the fakeClock. It
|
||||
// returns an updated slice of blockers (i.e. those still waiting)
|
||||
func notifyBlockers(blockers []*blocker, count int) (newBlockers []*blocker) {
|
||||
for _, b := range blockers {
|
||||
if b.count == count {
|
||||
close(b.ch)
|
||||
} else {
|
||||
newBlockers = append(newBlockers, b)
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// Sleep blocks until the given duration has passed on the fakeClock
|
||||
func (fc *fakeClock) Sleep(d time.Duration) {
|
||||
<-fc.After(d)
|
||||
}
|
||||
|
||||
// Time returns the current time of the fakeClock
|
||||
func (fc *fakeClock) Now() time.Time {
|
||||
fc.l.RLock()
|
||||
t := fc.time
|
||||
fc.l.RUnlock()
|
||||
return t
|
||||
}
|
||||
|
||||
// Advance advances fakeClock to a new point in time, ensuring channels from any
|
||||
// previous invocations of After are notified appropriately before returning
|
||||
func (fc *fakeClock) Advance(d time.Duration) {
|
||||
fc.l.Lock()
|
||||
defer fc.l.Unlock()
|
||||
end := fc.time.Add(d)
|
||||
var newSleepers []*sleeper
|
||||
for _, s := range fc.sleepers {
|
||||
if end.Sub(s.until) >= 0 {
|
||||
s.done <- end
|
||||
} else {
|
||||
newSleepers = append(newSleepers, s)
|
||||
}
|
||||
}
|
||||
fc.sleepers = newSleepers
|
||||
fc.blockers = notifyBlockers(fc.blockers, len(fc.sleepers))
|
||||
fc.time = end
|
||||
}
|
||||
|
||||
// BlockUntil will block until the fakeClock has the given number of sleepers
|
||||
// (callers of Sleep or After)
|
||||
func (fc *fakeClock) BlockUntil(n int) {
|
||||
fc.l.Lock()
|
||||
// Fast path: current number of sleepers is what we're looking for
|
||||
if len(fc.sleepers) == n {
|
||||
fc.l.Unlock()
|
||||
return
|
||||
}
|
||||
// Otherwise, set up a new blocker
|
||||
b := &blocker{
|
||||
count: n,
|
||||
ch: make(chan struct{}),
|
||||
}
|
||||
fc.blockers = append(fc.blockers, b)
|
||||
fc.l.Unlock()
|
||||
<-b.ch
|
||||
}
|
1
vendor/github.com/russellhaering/goxmldsig/.gitignore
generated
vendored
Normal file
1
vendor/github.com/russellhaering/goxmldsig/.gitignore
generated
vendored
Normal file
@ -0,0 +1 @@
|
||||
*.test
|
175
vendor/github.com/russellhaering/goxmldsig/LICENSE
generated
vendored
Normal file
175
vendor/github.com/russellhaering/goxmldsig/LICENSE
generated
vendored
Normal file
@ -0,0 +1,175 @@
|
||||
|
||||
Apache License
|
||||
Version 2.0, January 2004
|
||||
http://www.apache.org/licenses/
|
||||
|
||||
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
||||
|
||||
1. Definitions.
|
||||
|
||||
"License" shall mean the terms and conditions for use, reproduction,
|
||||
and distribution as defined by Sections 1 through 9 of this document.
|
||||
|
||||
"Licensor" shall mean the copyright owner or entity authorized by
|
||||
the copyright owner that is granting the License.
|
||||
|
||||
"Legal Entity" shall mean the union of the acting entity and all
|
||||
other entities that control, are controlled by, or are under common
|
||||
control with that entity. For the purposes of this definition,
|
||||
"control" means (i) the power, direct or indirect, to cause the
|
||||
direction or management of such entity, whether by contract or
|
||||
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
||||
outstanding shares, or (iii) beneficial ownership of such entity.
|
||||
|
||||
"You" (or "Your") shall mean an individual or Legal Entity
|
||||
exercising permissions granted by this License.
|
||||
|
||||
"Source" form shall mean the preferred form for making modifications,
|
||||
including but not limited to software source code, documentation
|
||||
source, and configuration files.
|
||||
|
||||
"Object" form shall mean any form resulting from mechanical
|
||||
transformation or translation of a Source form, including but
|
||||
not limited to compiled object code, generated documentation,
|
||||
and conversions to other media types.
|
||||
|
||||
"Work" shall mean the work of authorship, whether in Source or
|
||||
Object form, made available under the License, as indicated by a
|
||||
copyright notice that is included in or attached to the work
|
||||
(an example is provided in the Appendix below).
|
||||
|
||||
"Derivative Works" shall mean any work, whether in Source or Object
|
||||
form, that is based on (or derived from) the Work and for which the
|
||||
editorial revisions, annotations, elaborations, or other modifications
|
||||
represent, as a whole, an original work of authorship. For the purposes
|
||||
of this License, Derivative Works shall not include works that remain
|
||||
separable from, or merely link (or bind by name) to the interfaces of,
|
||||
the Work and Derivative Works thereof.
|
||||
|
||||
"Contribution" shall mean any work of authorship, including
|
||||
the original version of the Work and any modifications or additions
|
||||
to that Work or Derivative Works thereof, that is intentionally
|
||||
submitted to Licensor for inclusion in the Work by the copyright owner
|
||||
or by an individual or Legal Entity authorized to submit on behalf of
|
||||
the copyright owner. For the purposes of this definition, "submitted"
|
||||
means any form of electronic, verbal, or written communication sent
|
||||
to the Licensor or its representatives, including but not limited to
|
||||
communication on electronic mailing lists, source code control systems,
|
||||
and issue tracking systems that are managed by, or on behalf of, the
|
||||
Licensor for the purpose of discussing and improving the Work, but
|
||||
excluding communication that is conspicuously marked or otherwise
|
||||
designated in writing by the copyright owner as "Not a Contribution."
|
||||
|
||||
"Contributor" shall mean Licensor and any individual or Legal Entity
|
||||
on behalf of whom a Contribution has been received by Licensor and
|
||||
subsequently incorporated within the Work.
|
||||
|
||||
2. Grant of Copyright License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
copyright license to reproduce, prepare Derivative Works of,
|
||||
publicly display, publicly perform, sublicense, and distribute the
|
||||
Work and such Derivative Works in Source or Object form.
|
||||
|
||||
3. Grant of Patent License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
(except as stated in this section) patent license to make, have made,
|
||||
use, offer to sell, sell, import, and otherwise transfer the Work,
|
||||
where such license applies only to those patent claims licensable
|
||||
by such Contributor that are necessarily infringed by their
|
||||
Contribution(s) alone or by combination of their Contribution(s)
|
||||
with the Work to which such Contribution(s) was submitted. If You
|
||||
institute patent litigation against any entity (including a
|
||||
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
||||
or a Contribution incorporated within the Work constitutes direct
|
||||
or contributory patent infringement, then any patent licenses
|
||||
granted to You under this License for that Work shall terminate
|
||||
as of the date such litigation is filed.
|
||||
|
||||
4. Redistribution. You may reproduce and distribute copies of the
|
||||
Work or Derivative Works thereof in any medium, with or without
|
||||
modifications, and in Source or Object form, provided that You
|
||||
meet the following conditions:
|
||||
|
||||
(a) You must give any other recipients of the Work or
|
||||
Derivative Works a copy of this License; and
|
||||
|
||||
(b) You must cause any modified files to carry prominent notices
|
||||
stating that You changed the files; and
|
||||
|
||||
(c) You must retain, in the Source form of any Derivative Works
|
||||
that You distribute, all copyright, patent, trademark, and
|
||||
attribution notices from the Source form of the Work,
|
||||
excluding those notices that do not pertain to any part of
|
||||
the Derivative Works; and
|
||||
|
||||
(d) If the Work includes a "NOTICE" text file as part of its
|
||||
distribution, then any Derivative Works that You distribute must
|
||||
include a readable copy of the attribution notices contained
|
||||
within such NOTICE file, excluding those notices that do not
|
||||
pertain to any part of the Derivative Works, in at least one
|
||||
of the following places: within a NOTICE text file distributed
|
||||
as part of the Derivative Works; within the Source form or
|
||||
documentation, if provided along with the Derivative Works; or,
|
||||
within a display generated by the Derivative Works, if and
|
||||
wherever such third-party notices normally appear. The contents
|
||||
of the NOTICE file are for informational purposes only and
|
||||
do not modify the License. You may add Your own attribution
|
||||
notices within Derivative Works that You distribute, alongside
|
||||
or as an addendum to the NOTICE text from the Work, provided
|
||||
that such additional attribution notices cannot be construed
|
||||
as modifying the License.
|
||||
|
||||
You may add Your own copyright statement to Your modifications and
|
||||
may provide additional or different license terms and conditions
|
||||
for use, reproduction, or distribution of Your modifications, or
|
||||
for any such Derivative Works as a whole, provided Your use,
|
||||
reproduction, and distribution of the Work otherwise complies with
|
||||
the conditions stated in this License.
|
||||
|
||||
5. Submission of Contributions. Unless You explicitly state otherwise,
|
||||
any Contribution intentionally submitted for inclusion in the Work
|
||||
by You to the Licensor shall be under the terms and conditions of
|
||||
this License, without any additional terms or conditions.
|
||||
Notwithstanding the above, nothing herein shall supersede or modify
|
||||
the terms of any separate license agreement you may have executed
|
||||
with Licensor regarding such Contributions.
|
||||
|
||||
6. Trademarks. This License does not grant permission to use the trade
|
||||
names, trademarks, service marks, or product names of the Licensor,
|
||||
except as required for reasonable and customary use in describing the
|
||||
origin of the Work and reproducing the content of the NOTICE file.
|
||||
|
||||
7. Disclaimer of Warranty. Unless required by applicable law or
|
||||
agreed to in writing, Licensor provides the Work (and each
|
||||
Contributor provides its Contributions) on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
||||
implied, including, without limitation, any warranties or conditions
|
||||
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
||||
PARTICULAR PURPOSE. You are solely responsible for determining the
|
||||
appropriateness of using or redistributing the Work and assume any
|
||||
risks associated with Your exercise of permissions under this License.
|
||||
|
||||
8. Limitation of Liability. In no event and under no legal theory,
|
||||
whether in tort (including negligence), contract, or otherwise,
|
||||
unless required by applicable law (such as deliberate and grossly
|
||||
negligent acts) or agreed to in writing, shall any Contributor be
|
||||
liable to You for damages, including any direct, indirect, special,
|
||||
incidental, or consequential damages of any character arising as a
|
||||
result of this License or out of the use or inability to use the
|
||||
Work (including but not limited to damages for loss of goodwill,
|
||||
work stoppage, computer failure or malfunction, or any and all
|
||||
other commercial damages or losses), even if such Contributor
|
||||
has been advised of the possibility of such damages.
|
||||
|
||||
9. Accepting Warranty or Additional Liability. While redistributing
|
||||
the Work or Derivative Works thereof, You may choose to offer,
|
||||
and charge a fee for, acceptance of support, warranty, indemnity,
|
||||
or other liability obligations and/or rights consistent with this
|
||||
License. However, in accepting such obligations, You may act only
|
||||
on Your own behalf and on Your sole responsibility, not on behalf
|
||||
of any other Contributor, and only if You agree to indemnify,
|
||||
defend, and hold each Contributor harmless for any liability
|
||||
incurred by, or claims asserted against, such Contributor by reason
|
||||
of your accepting any such warranty or additional liability.
|
90
vendor/github.com/russellhaering/goxmldsig/README.md
generated
vendored
Normal file
90
vendor/github.com/russellhaering/goxmldsig/README.md
generated
vendored
Normal file
@ -0,0 +1,90 @@
|
||||
# goxmldsig
|
||||
|
||||
[](https://travis-ci.org/russellhaering/goxmldsig)
|
||||
[](https://godoc.org/github.com/russellhaering/goxmldsig)
|
||||
|
||||
XML Digital Signatures implemented in pure Go.
|
||||
|
||||
## Installation
|
||||
|
||||
Install `goxmldsig` into your `$GOPATH` using `go get`:
|
||||
|
||||
```
|
||||
$ go get github.com/russellhaering/goxmldsig
|
||||
```
|
||||
|
||||
## Usage
|
||||
|
||||
### Signing
|
||||
|
||||
```go
|
||||
package main
|
||||
|
||||
import (
|
||||
"github.com/beevik/etree"
|
||||
"github.com/russellhaering/goxmldsig"
|
||||
)
|
||||
|
||||
func main() {
|
||||
// Generate a key and self-signed certificate for signing
|
||||
randomKeyStore := dsig.RandomKeyStoreForTest()
|
||||
ctx := dsig.NewDefaultSigningContext(randomKeyStore)
|
||||
elementToSign := &etree.Element{
|
||||
Tag: "ExampleElement",
|
||||
}
|
||||
elementToSign.CreateAttr("ID", "id1234")
|
||||
|
||||
// Sign the element
|
||||
signedElement, err := ctx.SignEnveloped(elementToSign)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
// Serialize the signed element. It is important not to modify the element
|
||||
// after it has been signed - even pretty-printing the XML will invalidate
|
||||
// the signature.
|
||||
doc := etree.NewDocument()
|
||||
doc.SetRoot(signedElement)
|
||||
str, err := doc.WriteToString()
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
println(str)
|
||||
}
|
||||
```
|
||||
|
||||
### Signature Validation
|
||||
|
||||
```go
|
||||
// Validate an element against a root certificate
|
||||
func validate(root *x509.Certificate, el *etree.Element) {
|
||||
// Construct a signing context with one or more roots of trust.
|
||||
ctx := dsig.NewDefaultValidationContext(&dsig.MemoryX509CertificateStore{
|
||||
Roots: []*x509.Certificate{root},
|
||||
})
|
||||
|
||||
// It is important to only use the returned validated element.
|
||||
// See: https://www.w3.org/TR/xmldsig-bestpractices/#check-what-is-signed
|
||||
validated, err := ctx.Validate(el)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
doc := etree.NewDocument()
|
||||
doc.SetRoot(validated)
|
||||
str, err := doc.WriteToString()
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
println(str)
|
||||
}
|
||||
```
|
||||
|
||||
## Limitations
|
||||
|
||||
This library was created in order to [implement SAML 2.0](https://github.com/russellhaering/gosaml2)
|
||||
without needing to execute a command line tool to create and validate signatures. It currently
|
||||
only implements the subset of relevant standards needed to support that implementation, but
|
||||
I hope to make it more complete over time. Contributions are welcome.
|
162
vendor/github.com/russellhaering/goxmldsig/canonicalize.go
generated
vendored
Normal file
162
vendor/github.com/russellhaering/goxmldsig/canonicalize.go
generated
vendored
Normal file
@ -0,0 +1,162 @@
|
||||
package dsig
|
||||
|
||||
import (
|
||||
"sort"
|
||||
|
||||
"github.com/beevik/etree"
|
||||
"github.com/russellhaering/goxmldsig/etreeutils"
|
||||
)
|
||||
|
||||
// Canonicalizer is an implementation of a canonicalization algorithm.
|
||||
type Canonicalizer interface {
|
||||
Canonicalize(el *etree.Element) ([]byte, error)
|
||||
Algorithm() AlgorithmID
|
||||
}
|
||||
|
||||
type c14N10ExclusiveCanonicalizer struct {
|
||||
prefixList string
|
||||
}
|
||||
|
||||
// MakeC14N10ExclusiveCanonicalizerWithPrefixList constructs an exclusive Canonicalizer
|
||||
// from a PrefixList in NMTOKENS format (a white space separated list).
|
||||
func MakeC14N10ExclusiveCanonicalizerWithPrefixList(prefixList string) Canonicalizer {
|
||||
return &c14N10ExclusiveCanonicalizer{
|
||||
prefixList: prefixList,
|
||||
}
|
||||
}
|
||||
|
||||
// Canonicalize transforms the input Element into a serialized XML document in canonical form.
|
||||
func (c *c14N10ExclusiveCanonicalizer) Canonicalize(el *etree.Element) ([]byte, error) {
|
||||
err := etreeutils.TransformExcC14n(el, c.prefixList)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return canonicalSerialize(el)
|
||||
}
|
||||
|
||||
func (c *c14N10ExclusiveCanonicalizer) Algorithm() AlgorithmID {
|
||||
return CanonicalXML10ExclusiveAlgorithmId
|
||||
}
|
||||
|
||||
type c14N11Canonicalizer struct{}
|
||||
|
||||
// MakeC14N11Canonicalizer constructs an inclusive canonicalizer.
|
||||
func MakeC14N11Canonicalizer() Canonicalizer {
|
||||
return &c14N11Canonicalizer{}
|
||||
}
|
||||
|
||||
// Canonicalize transforms the input Element into a serialized XML document in canonical form.
|
||||
func (c *c14N11Canonicalizer) Canonicalize(el *etree.Element) ([]byte, error) {
|
||||
scope := make(map[string]struct{})
|
||||
return canonicalSerialize(canonicalPrep(el, scope))
|
||||
}
|
||||
|
||||
func (c *c14N11Canonicalizer) Algorithm() AlgorithmID {
|
||||
return CanonicalXML11AlgorithmId
|
||||
}
|
||||
|
||||
type c14N10RecCanonicalizer struct{}
|
||||
|
||||
// MakeC14N10RecCanonicalizer constructs an inclusive canonicalizer.
|
||||
func MakeC14N10RecCanonicalizer() Canonicalizer {
|
||||
return &c14N10RecCanonicalizer{}
|
||||
}
|
||||
|
||||
// Canonicalize transforms the input Element into a serialized XML document in canonical form.
|
||||
func (c *c14N10RecCanonicalizer) Canonicalize(el *etree.Element) ([]byte, error) {
|
||||
scope := make(map[string]struct{})
|
||||
return canonicalSerialize(canonicalPrep(el, scope))
|
||||
}
|
||||
|
||||
func (c *c14N10RecCanonicalizer) Algorithm() AlgorithmID {
|
||||
return CanonicalXML10RecAlgorithmId
|
||||
}
|
||||
|
||||
type c14N10CommentCanonicalizer struct{}
|
||||
|
||||
// MakeC14N10CommentCanonicalizer constructs an inclusive canonicalizer.
|
||||
func MakeC14N10CommentCanonicalizer() Canonicalizer {
|
||||
return &c14N10CommentCanonicalizer{}
|
||||
}
|
||||
|
||||
// Canonicalize transforms the input Element into a serialized XML document in canonical form.
|
||||
func (c *c14N10CommentCanonicalizer) Canonicalize(el *etree.Element) ([]byte, error) {
|
||||
scope := make(map[string]struct{})
|
||||
return canonicalSerialize(canonicalPrep(el, scope))
|
||||
}
|
||||
|
||||
func (c *c14N10CommentCanonicalizer) Algorithm() AlgorithmID {
|
||||
return CanonicalXML10CommentAlgorithmId
|
||||
}
|
||||
|
||||
func composeAttr(space, key string) string {
|
||||
if space != "" {
|
||||
return space + ":" + key
|
||||
}
|
||||
|
||||
return key
|
||||
}
|
||||
|
||||
type c14nSpace struct {
|
||||
a etree.Attr
|
||||
used bool
|
||||
}
|
||||
|
||||
const nsSpace = "xmlns"
|
||||
|
||||
// canonicalPrep accepts an *etree.Element and transforms it into one which is ready
|
||||
// for serialization into inclusive canonical form. Specifically this
|
||||
// entails:
|
||||
//
|
||||
// 1. Stripping re-declarations of namespaces
|
||||
// 2. Sorting attributes into canonical order
|
||||
//
|
||||
// Inclusive canonicalization does not strip unused namespaces.
|
||||
//
|
||||
// TODO(russell_h): This is very similar to excCanonicalPrep - perhaps they should
|
||||
// be unified into one parameterized function?
|
||||
func canonicalPrep(el *etree.Element, seenSoFar map[string]struct{}) *etree.Element {
|
||||
_seenSoFar := make(map[string]struct{})
|
||||
for k, v := range seenSoFar {
|
||||
_seenSoFar[k] = v
|
||||
}
|
||||
|
||||
ne := el.Copy()
|
||||
sort.Sort(etreeutils.SortedAttrs(ne.Attr))
|
||||
if len(ne.Attr) != 0 {
|
||||
for _, attr := range ne.Attr {
|
||||
if attr.Space != nsSpace {
|
||||
continue
|
||||
}
|
||||
key := attr.Space + ":" + attr.Key
|
||||
if _, seen := _seenSoFar[key]; seen {
|
||||
ne.RemoveAttr(attr.Space + ":" + attr.Key)
|
||||
} else {
|
||||
_seenSoFar[key] = struct{}{}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for i, token := range ne.Child {
|
||||
childElement, ok := token.(*etree.Element)
|
||||
if ok {
|
||||
ne.Child[i] = canonicalPrep(childElement, _seenSoFar)
|
||||
}
|
||||
}
|
||||
|
||||
return ne
|
||||
}
|
||||
|
||||
func canonicalSerialize(el *etree.Element) ([]byte, error) {
|
||||
doc := etree.NewDocument()
|
||||
doc.SetRoot(el.Copy())
|
||||
|
||||
doc.WriteSettings = etree.WriteSettings{
|
||||
CanonicalAttrVal: true,
|
||||
CanonicalEndTags: true,
|
||||
CanonicalText: true,
|
||||
}
|
||||
|
||||
return doc.WriteToBytes()
|
||||
}
|
55
vendor/github.com/russellhaering/goxmldsig/clock.go
generated
vendored
Normal file
55
vendor/github.com/russellhaering/goxmldsig/clock.go
generated
vendored
Normal file
@ -0,0 +1,55 @@
|
||||
package dsig
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
"github.com/jonboulle/clockwork"
|
||||
)
|
||||
|
||||
// Clock wraps a clockwork.Clock (which could be real or fake) in order
|
||||
// to default to a real clock when a nil *Clock is used. In other words,
|
||||
// if you attempt to use a nil *Clock it will defer to the real system
|
||||
// clock. This allows Clock to be easily added to structs with methods
|
||||
// that currently reference the time package, without requiring every
|
||||
// instantiation of that struct to be updated.
|
||||
type Clock struct {
|
||||
wrapped clockwork.Clock
|
||||
}
|
||||
|
||||
func (c *Clock) getWrapped() clockwork.Clock {
|
||||
if c == nil {
|
||||
return clockwork.NewRealClock()
|
||||
}
|
||||
|
||||
return c.wrapped
|
||||
}
|
||||
|
||||
func (c *Clock) After(d time.Duration) <-chan time.Time {
|
||||
return c.getWrapped().After(d)
|
||||
}
|
||||
|
||||
func (c *Clock) Sleep(d time.Duration) {
|
||||
c.getWrapped().Sleep(d)
|
||||
}
|
||||
|
||||
func (c *Clock) Now() time.Time {
|
||||
return c.getWrapped().Now()
|
||||
}
|
||||
|
||||
func NewRealClock() *Clock {
|
||||
return &Clock{
|
||||
wrapped: clockwork.NewRealClock(),
|
||||
}
|
||||
}
|
||||
|
||||
func NewFakeClock(wrapped clockwork.Clock) *Clock {
|
||||
return &Clock{
|
||||
wrapped: wrapped,
|
||||
}
|
||||
}
|
||||
|
||||
func NewFakeClockAt(t time.Time) *Clock {
|
||||
return &Clock{
|
||||
wrapped: clockwork.NewFakeClockAt(t),
|
||||
}
|
||||
}
|
98
vendor/github.com/russellhaering/goxmldsig/etreeutils/canonicalize.go
generated
vendored
Normal file
98
vendor/github.com/russellhaering/goxmldsig/etreeutils/canonicalize.go
generated
vendored
Normal file
@ -0,0 +1,98 @@
|
||||
package etreeutils
|
||||
|
||||
import (
|
||||
"sort"
|
||||
"strings"
|
||||
|
||||
"github.com/beevik/etree"
|
||||
)
|
||||
|
||||
// TransformExcC14n transforms the passed element into xml-exc-c14n form.
|
||||
func TransformExcC14n(el *etree.Element, inclusiveNamespacesPrefixList string) error {
|
||||
prefixes := strings.Fields(inclusiveNamespacesPrefixList)
|
||||
prefixSet := make(map[string]struct{}, len(prefixes))
|
||||
|
||||
for _, prefix := range prefixes {
|
||||
prefixSet[prefix] = struct{}{}
|
||||
}
|
||||
|
||||
err := transformExcC14n(DefaultNSContext, DefaultNSContext, el, prefixSet)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func transformExcC14n(ctx, declared NSContext, el *etree.Element, inclusiveNamespaces map[string]struct{}) error {
|
||||
scope, err := ctx.SubContext(el)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
visiblyUtilizedPrefixes := map[string]struct{}{
|
||||
el.Space: struct{}{},
|
||||
}
|
||||
|
||||
filteredAttrs := []etree.Attr{}
|
||||
|
||||
// Filter out all namespace declarations
|
||||
for _, attr := range el.Attr {
|
||||
switch {
|
||||
case attr.Space == xmlnsPrefix:
|
||||
if _, ok := inclusiveNamespaces[attr.Key]; ok {
|
||||
visiblyUtilizedPrefixes[attr.Key] = struct{}{}
|
||||
}
|
||||
|
||||
case attr.Space == defaultPrefix && attr.Key == xmlnsPrefix:
|
||||
if _, ok := inclusiveNamespaces[defaultPrefix]; ok {
|
||||
visiblyUtilizedPrefixes[defaultPrefix] = struct{}{}
|
||||
}
|
||||
|
||||
default:
|
||||
if attr.Space != defaultPrefix {
|
||||
visiblyUtilizedPrefixes[attr.Space] = struct{}{}
|
||||
}
|
||||
|
||||
filteredAttrs = append(filteredAttrs, attr)
|
||||
}
|
||||
}
|
||||
|
||||
el.Attr = filteredAttrs
|
||||
|
||||
declared = declared.Copy()
|
||||
|
||||
// Declare all visibly utilized prefixes that are in-scope but haven't
|
||||
// been declared in the canonicalized form yet. These might have been
|
||||
// declared on this element but then filtered out above, or they might
|
||||
// have been declared on an ancestor (before canonicalization) which
|
||||
// didn't visibly utilize and thus had them removed.
|
||||
for prefix := range visiblyUtilizedPrefixes {
|
||||
// Skip redundant declarations - they have to already have the same
|
||||
// value.
|
||||
if declaredNamespace, ok := declared.prefixes[prefix]; ok {
|
||||
if value, ok := scope.prefixes[prefix]; ok && declaredNamespace == value {
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
namespace, err := scope.LookupPrefix(prefix)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
el.Attr = append(el.Attr, declared.declare(prefix, namespace))
|
||||
}
|
||||
|
||||
sort.Sort(SortedAttrs(el.Attr))
|
||||
|
||||
// Transform child elements
|
||||
for _, child := range el.ChildElements() {
|
||||
err := transformExcC14n(scope, declared, child, inclusiveNamespaces)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
407
vendor/github.com/russellhaering/goxmldsig/etreeutils/namespace.go
generated
vendored
Normal file
407
vendor/github.com/russellhaering/goxmldsig/etreeutils/namespace.go
generated
vendored
Normal file
@ -0,0 +1,407 @@
|
||||
package etreeutils
|
||||
|
||||
import (
|
||||
"errors"
|
||||
|
||||
"fmt"
|
||||
|
||||
"sort"
|
||||
|
||||
"github.com/beevik/etree"
|
||||
)
|
||||
|
||||
const (
|
||||
defaultPrefix = ""
|
||||
xmlnsPrefix = "xmlns"
|
||||
xmlPrefix = "xml"
|
||||
|
||||
XMLNamespace = "http://www.w3.org/XML/1998/namespace"
|
||||
XMLNSNamespace = "http://www.w3.org/2000/xmlns/"
|
||||
)
|
||||
|
||||
var (
|
||||
DefaultNSContext = NSContext{
|
||||
prefixes: map[string]string{
|
||||
defaultPrefix: XMLNamespace,
|
||||
xmlPrefix: XMLNamespace,
|
||||
xmlnsPrefix: XMLNSNamespace,
|
||||
},
|
||||
}
|
||||
|
||||
EmptyNSContext = NSContext{}
|
||||
|
||||
ErrReservedNamespace = errors.New("disallowed declaration of reserved namespace")
|
||||
ErrInvalidDefaultNamespace = errors.New("invalid default namespace declaration")
|
||||
ErrTraversalHalted = errors.New("traversal halted")
|
||||
)
|
||||
|
||||
type ErrUndeclaredNSPrefix struct {
|
||||
Prefix string
|
||||
}
|
||||
|
||||
func (e ErrUndeclaredNSPrefix) Error() string {
|
||||
return fmt.Sprintf("undeclared namespace prefix: '%s'", e.Prefix)
|
||||
}
|
||||
|
||||
type NSContext struct {
|
||||
prefixes map[string]string
|
||||
}
|
||||
|
||||
func (ctx NSContext) Copy() NSContext {
|
||||
prefixes := make(map[string]string, len(ctx.prefixes)+4)
|
||||
for k, v := range ctx.prefixes {
|
||||
prefixes[k] = v
|
||||
}
|
||||
|
||||
return NSContext{prefixes: prefixes}
|
||||
}
|
||||
|
||||
func (ctx NSContext) declare(prefix, namespace string) etree.Attr {
|
||||
ctx.prefixes[prefix] = namespace
|
||||
|
||||
switch prefix {
|
||||
case defaultPrefix:
|
||||
return etree.Attr{
|
||||
Key: xmlnsPrefix,
|
||||
Value: namespace,
|
||||
}
|
||||
|
||||
default:
|
||||
return etree.Attr{
|
||||
Space: xmlnsPrefix,
|
||||
Key: prefix,
|
||||
Value: namespace,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (ctx NSContext) SubContext(el *etree.Element) (NSContext, error) {
|
||||
// The subcontext should inherit existing declared prefixes
|
||||
newCtx := ctx.Copy()
|
||||
|
||||
// Merge new namespace declarations on top of existing ones.
|
||||
for _, attr := range el.Attr {
|
||||
if attr.Space == xmlnsPrefix {
|
||||
// This attribute is a namespace declaration of the form "xmlns:<prefix>"
|
||||
|
||||
// The 'xml' namespace may only be re-declared with the name 'http://www.w3.org/XML/1998/namespace'
|
||||
if attr.Key == xmlPrefix && attr.Value != XMLNamespace {
|
||||
return ctx, ErrReservedNamespace
|
||||
}
|
||||
|
||||
// The 'xmlns' namespace may not be re-declared
|
||||
if attr.Key == xmlnsPrefix {
|
||||
return ctx, ErrReservedNamespace
|
||||
}
|
||||
|
||||
newCtx.declare(attr.Key, attr.Value)
|
||||
} else if attr.Space == defaultPrefix && attr.Key == xmlnsPrefix {
|
||||
// This attribute is a default namespace declaration
|
||||
|
||||
// The xmlns namespace value may not be declared as the default namespace
|
||||
if attr.Value == XMLNSNamespace {
|
||||
return ctx, ErrInvalidDefaultNamespace
|
||||
}
|
||||
|
||||
newCtx.declare(defaultPrefix, attr.Value)
|
||||
}
|
||||
}
|
||||
|
||||
return newCtx, nil
|
||||
}
|
||||
|
||||
// Prefixes returns a copy of this context's prefix map.
|
||||
func (ctx NSContext) Prefixes() map[string]string {
|
||||
prefixes := make(map[string]string, len(ctx.prefixes))
|
||||
for k, v := range ctx.prefixes {
|
||||
prefixes[k] = v
|
||||
}
|
||||
|
||||
return prefixes
|
||||
}
|
||||
|
||||
// LookupPrefix attempts to find a declared namespace for the specified prefix. If the prefix
|
||||
// is an empty string this will be the default namespace for this context. If the prefix is
|
||||
// undeclared in this context an ErrUndeclaredNSPrefix will be returned.
|
||||
func (ctx NSContext) LookupPrefix(prefix string) (string, error) {
|
||||
if namespace, ok := ctx.prefixes[prefix]; ok {
|
||||
return namespace, nil
|
||||
}
|
||||
|
||||
return "", ErrUndeclaredNSPrefix{
|
||||
Prefix: prefix,
|
||||
}
|
||||
}
|
||||
|
||||
// NSIterHandler is a function which is invoked with a element and its surrounding
|
||||
// NSContext during traversals.
|
||||
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)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err = handle(ctx, el)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Recursively traverse child elements.
|
||||
for _, child := range el.ChildElements() {
|
||||
err := NSTraverse(ctx, child, handle)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// NSDetatch makes a copy of the passed element, and declares any namespaces in
|
||||
// the passed context onto the new element before returning it.
|
||||
func NSDetatch(ctx NSContext, el *etree.Element) (*etree.Element, error) {
|
||||
ctx, err := ctx.SubContext(el)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
el = el.Copy()
|
||||
|
||||
// Build a new attribute list
|
||||
attrs := make([]etree.Attr, 0, len(el.Attr))
|
||||
|
||||
// First copy over anything that isn't a namespace declaration
|
||||
for _, attr := range el.Attr {
|
||||
if attr.Space == xmlnsPrefix {
|
||||
continue
|
||||
}
|
||||
|
||||
if attr.Space == defaultPrefix && attr.Key == xmlnsPrefix {
|
||||
continue
|
||||
}
|
||||
|
||||
attrs = append(attrs, attr)
|
||||
}
|
||||
|
||||
// Append all in-context namespace declarations
|
||||
for prefix, namespace := range ctx.prefixes {
|
||||
// Skip the implicit "xml" and "xmlns" prefix declarations
|
||||
if prefix == xmlnsPrefix || prefix == xmlPrefix {
|
||||
continue
|
||||
}
|
||||
|
||||
// Also skip declararing the default namespace as XMLNamespace
|
||||
if prefix == defaultPrefix && namespace == XMLNamespace {
|
||||
continue
|
||||
}
|
||||
|
||||
if prefix != defaultPrefix {
|
||||
attrs = append(attrs, etree.Attr{
|
||||
Space: xmlnsPrefix,
|
||||
Key: prefix,
|
||||
Value: namespace,
|
||||
})
|
||||
} else {
|
||||
attrs = append(attrs, etree.Attr{
|
||||
Key: xmlnsPrefix,
|
||||
Value: namespace,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
sort.Sort(SortedAttrs(attrs))
|
||||
|
||||
el.Attr = attrs
|
||||
|
||||
return el, nil
|
||||
}
|
||||
|
||||
// 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)
|
||||
}
|
||||
|
||||
// NSSelectOneCtx conducts a depth-first search for an element with the specified namespace
|
||||
// and tag. If such an element is found, a new *etree.Element is returned which is a
|
||||
// copy of the found element, but with all in-context namespace declarations attached
|
||||
// to the element as attributes.
|
||||
func NSSelectOneCtx(ctx NSContext, el *etree.Element, namespace, tag string) (*etree.Element, error) {
|
||||
var found *etree.Element
|
||||
|
||||
err := NSFindIterateCtx(ctx, el, namespace, tag, func(ctx NSContext, el *etree.Element) error {
|
||||
var err error
|
||||
|
||||
found, err = NSDetatch(ctx, el)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return ErrTraversalHalted
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return found, nil
|
||||
}
|
||||
|
||||
// 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)
|
||||
}
|
||||
|
||||
// NSFindIterateCtx conducts a depth-first traversal searching for elements with the
|
||||
// specified tag in the specified namespace. It uses the passed NSContext for prefix
|
||||
// lookups. For each such element, the passed handler function is invoked. If the
|
||||
// handler function returns an error traversal is immediately halted. If the error
|
||||
// returned by the handler is ErrTraversalHalted then nil will be returned by
|
||||
// NSFindIterate. If any other error is returned by the handler, that error will be
|
||||
// returned by NSFindIterate.
|
||||
func NSFindIterateCtx(ctx NSContext, el *etree.Element, namespace, tag string, handle NSIterHandler) error {
|
||||
err := NSTraverse(ctx, el, func(ctx NSContext, el *etree.Element) error {
|
||||
_ctx, err := ctx.SubContext(el)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
currentNS, err := _ctx.LookupPrefix(el.Space)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Base case, el is the sought after element.
|
||||
if currentNS == namespace && el.Tag == tag {
|
||||
return handle(ctx, el)
|
||||
}
|
||||
|
||||
return nil
|
||||
})
|
||||
|
||||
if err != nil && err != ErrTraversalHalted {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// 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)
|
||||
}
|
||||
|
||||
// NSFindOneCtx conducts a depth-first search for the specified element. If such an element
|
||||
// is found a reference to it is returned.
|
||||
func NSFindOneCtx(ctx NSContext, el *etree.Element, namespace, tag string) (*etree.Element, error) {
|
||||
var found *etree.Element
|
||||
|
||||
err := NSFindIterateCtx(ctx, el, namespace, tag, func(ctx NSContext, el *etree.Element) error {
|
||||
found = el
|
||||
return ErrTraversalHalted
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return found, nil
|
||||
}
|
||||
|
||||
// NSIterateChildren iterates the children of an element, invoking the passed
|
||||
// handler with each direct child of the element, and the context surrounding
|
||||
// that child.
|
||||
func NSIterateChildren(ctx NSContext, el *etree.Element, handle NSIterHandler) error {
|
||||
ctx, err := ctx.SubContext(el)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Iterate the child elements.
|
||||
for _, child := range el.ChildElements() {
|
||||
err = handle(ctx, child)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// NSFindIterateChildrenCtx takes an element and its surrounding context, and iterates
|
||||
// the children of that element searching for an element matching the passed namespace
|
||||
// and tag. For each such element that is found, handle is invoked with the matched
|
||||
// element and its own surrounding context.
|
||||
func NSFindChildrenIterateCtx(ctx NSContext, el *etree.Element, namespace, tag string, handle NSIterHandler) error {
|
||||
err := NSIterateChildren(ctx, el, func(ctx NSContext, el *etree.Element) error {
|
||||
_ctx, err := ctx.SubContext(el)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
currentNS, err := _ctx.LookupPrefix(el.Space)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Base case, el is the sought after element.
|
||||
if currentNS == namespace && el.Tag == tag {
|
||||
return handle(ctx, el)
|
||||
}
|
||||
|
||||
return nil
|
||||
})
|
||||
|
||||
if err != nil && err != ErrTraversalHalted {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// 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)
|
||||
}
|
||||
|
||||
// NSFindOneCtx conducts a depth-first search for the specified element. If such an
|
||||
// element is found a reference to it is returned.
|
||||
func NSFindOneChildCtx(ctx NSContext, el *etree.Element, namespace, tag string) (*etree.Element, error) {
|
||||
var found *etree.Element
|
||||
|
||||
err := NSFindChildrenIterateCtx(ctx, el, namespace, tag, func(ctx NSContext, el *etree.Element) error {
|
||||
found = el
|
||||
return ErrTraversalHalted
|
||||
})
|
||||
|
||||
if err != nil && err != ErrTraversalHalted {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return found, nil
|
||||
}
|
||||
|
||||
// NSBuildParentContext recurses upward from an element in order to build an NSContext
|
||||
// for its immediate parent. If the element has no parent DefaultNSContext
|
||||
// is returned.
|
||||
func NSBuildParentContext(el *etree.Element) (NSContext, error) {
|
||||
parent := el.Parent()
|
||||
if parent == nil {
|
||||
return DefaultNSContext, nil
|
||||
}
|
||||
|
||||
ctx, err := NSBuildParentContext(parent)
|
||||
|
||||
if err != nil {
|
||||
return ctx, err
|
||||
}
|
||||
|
||||
return ctx.SubContext(parent)
|
||||
}
|
66
vendor/github.com/russellhaering/goxmldsig/etreeutils/sort.go
generated
vendored
Normal file
66
vendor/github.com/russellhaering/goxmldsig/etreeutils/sort.go
generated
vendored
Normal file
@ -0,0 +1,66 @@
|
||||
package etreeutils
|
||||
|
||||
import "github.com/beevik/etree"
|
||||
|
||||
// SortedAttrs provides sorting capabilities, compatible with XML C14N, on top
|
||||
// of an []etree.Attr
|
||||
type SortedAttrs []etree.Attr
|
||||
|
||||
func (a SortedAttrs) Len() int {
|
||||
return len(a)
|
||||
}
|
||||
|
||||
func (a SortedAttrs) Swap(i, j int) {
|
||||
a[i], a[j] = a[j], a[i]
|
||||
}
|
||||
|
||||
func (a SortedAttrs) Less(i, j int) bool {
|
||||
// This is the best reference I've found on sort order:
|
||||
// http://dst.lbl.gov/~ksb/Scratch/XMLC14N.html
|
||||
|
||||
// If attr j is a default namespace declaration, attr i may
|
||||
// not be strictly "less" than it.
|
||||
if a[j].Space == defaultPrefix && a[j].Key == xmlnsPrefix {
|
||||
return false
|
||||
}
|
||||
|
||||
// Otherwise, if attr i is a default namespace declaration, it
|
||||
// must be less than anything else.
|
||||
if a[i].Space == defaultPrefix && a[i].Key == xmlnsPrefix {
|
||||
return true
|
||||
}
|
||||
|
||||
// Next, namespace prefix declarations, sorted by prefix, come before
|
||||
// anythign else.
|
||||
if a[i].Space == xmlnsPrefix {
|
||||
if a[j].Space == xmlnsPrefix {
|
||||
return a[i].Key < a[j].Key
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
if a[j].Space == xmlnsPrefix {
|
||||
return false
|
||||
}
|
||||
|
||||
// Then come unprefixed attributes, sorted by key.
|
||||
if a[i].Space == defaultPrefix {
|
||||
if a[j].Space == defaultPrefix {
|
||||
return a[i].Key < a[j].Key
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
if a[j].Space == defaultPrefix {
|
||||
return false
|
||||
}
|
||||
|
||||
// Wow. We're still going. Finally, attributes in the same namespace should be
|
||||
// sorted by key. Attributes in different namespaces should be sorted by the
|
||||
// actual namespace (_not_ the prefix). For now just use the prefix.
|
||||
if a[i].Space == a[j].Space {
|
||||
return a[i].Key < a[j].Key
|
||||
}
|
||||
|
||||
return a[i].Space < a[j].Space
|
||||
}
|
43
vendor/github.com/russellhaering/goxmldsig/etreeutils/unmarshal.go
generated
vendored
Normal file
43
vendor/github.com/russellhaering/goxmldsig/etreeutils/unmarshal.go
generated
vendored
Normal file
@ -0,0 +1,43 @@
|
||||
package etreeutils
|
||||
|
||||
import (
|
||||
"encoding/xml"
|
||||
|
||||
"github.com/beevik/etree"
|
||||
)
|
||||
|
||||
// NSUnmarshalElement unmarshals the passed etree Element into the value pointed to by
|
||||
// v using encoding/xml in the context of the passed NSContext. If v implements
|
||||
// ElementKeeper, SetUnderlyingElement will be called on v with a reference to el.
|
||||
func NSUnmarshalElement(ctx NSContext, el *etree.Element, v interface{}) error {
|
||||
detatched, err := NSDetatch(ctx, el)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
doc := etree.NewDocument()
|
||||
doc.AddChild(detatched)
|
||||
data, err := doc.WriteToBytes()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err = xml.Unmarshal(data, v)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
switch v := v.(type) {
|
||||
case ElementKeeper:
|
||||
v.SetUnderlyingElement(el)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// ElementKeeper should be implemented by types which will be passed to
|
||||
// UnmarshalElement, but wish to keep a reference
|
||||
type ElementKeeper interface {
|
||||
SetUnderlyingElement(*etree.Element)
|
||||
UnderlyingElement() *etree.Element
|
||||
}
|
67
vendor/github.com/russellhaering/goxmldsig/keystore.go
generated
vendored
Normal file
67
vendor/github.com/russellhaering/goxmldsig/keystore.go
generated
vendored
Normal file
@ -0,0 +1,67 @@
|
||||
package dsig
|
||||
|
||||
import (
|
||||
"crypto/rand"
|
||||
"crypto/rsa"
|
||||
"crypto/x509"
|
||||
"math/big"
|
||||
"time"
|
||||
)
|
||||
|
||||
type X509KeyStore interface {
|
||||
GetKeyPair() (privateKey *rsa.PrivateKey, cert []byte, err error)
|
||||
}
|
||||
|
||||
type X509ChainStore interface {
|
||||
GetChain() (certs [][]byte, err error)
|
||||
}
|
||||
|
||||
type X509CertificateStore interface {
|
||||
Certificates() (roots []*x509.Certificate, err error)
|
||||
}
|
||||
|
||||
type MemoryX509CertificateStore struct {
|
||||
Roots []*x509.Certificate
|
||||
}
|
||||
|
||||
func (mX509cs *MemoryX509CertificateStore) Certificates() ([]*x509.Certificate, error) {
|
||||
return mX509cs.Roots, nil
|
||||
}
|
||||
|
||||
type MemoryX509KeyStore struct {
|
||||
privateKey *rsa.PrivateKey
|
||||
cert []byte
|
||||
}
|
||||
|
||||
func (ks *MemoryX509KeyStore) GetKeyPair() (*rsa.PrivateKey, []byte, error) {
|
||||
return ks.privateKey, ks.cert, nil
|
||||
}
|
||||
|
||||
func RandomKeyStoreForTest() X509KeyStore {
|
||||
key, err := rsa.GenerateKey(rand.Reader, 1024)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
now := time.Now()
|
||||
|
||||
template := &x509.Certificate{
|
||||
SerialNumber: big.NewInt(0),
|
||||
NotBefore: now.Add(-5 * time.Minute),
|
||||
NotAfter: now.Add(365 * 24 * time.Hour),
|
||||
|
||||
KeyUsage: x509.KeyUsageDigitalSignature,
|
||||
ExtKeyUsage: []x509.ExtKeyUsage{},
|
||||
BasicConstraintsValid: true,
|
||||
}
|
||||
|
||||
cert, err := x509.CreateCertificate(rand.Reader, template, template, &key.PublicKey, key)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
return &MemoryX509KeyStore{
|
||||
privateKey: key,
|
||||
cert: cert,
|
||||
}
|
||||
}
|
12
vendor/github.com/russellhaering/goxmldsig/run_test.sh
generated
vendored
Normal file
12
vendor/github.com/russellhaering/goxmldsig/run_test.sh
generated
vendored
Normal file
@ -0,0 +1,12 @@
|
||||
#!/bin/bash
|
||||
cd `dirname $0`
|
||||
DIRS=`git grep -l 'func Test' | xargs dirname | sort -u`
|
||||
for DIR in $DIRS
|
||||
do
|
||||
echo
|
||||
echo "dir: $DIR"
|
||||
echo "======================================"
|
||||
pushd $DIR >/dev/null
|
||||
go test -v || exit 1
|
||||
popd >/dev/null
|
||||
done
|
256
vendor/github.com/russellhaering/goxmldsig/sign.go
generated
vendored
Normal file
256
vendor/github.com/russellhaering/goxmldsig/sign.go
generated
vendored
Normal file
@ -0,0 +1,256 @@
|
||||
package dsig
|
||||
|
||||
import (
|
||||
"crypto"
|
||||
"crypto/rand"
|
||||
"crypto/rsa"
|
||||
_ "crypto/sha1"
|
||||
_ "crypto/sha256"
|
||||
"encoding/base64"
|
||||
"errors"
|
||||
"fmt"
|
||||
|
||||
"github.com/beevik/etree"
|
||||
"github.com/russellhaering/goxmldsig/etreeutils"
|
||||
)
|
||||
|
||||
type SigningContext struct {
|
||||
Hash crypto.Hash
|
||||
KeyStore X509KeyStore
|
||||
IdAttribute string
|
||||
Prefix string
|
||||
Canonicalizer Canonicalizer
|
||||
}
|
||||
|
||||
func NewDefaultSigningContext(ks X509KeyStore) *SigningContext {
|
||||
return &SigningContext{
|
||||
Hash: crypto.SHA256,
|
||||
KeyStore: ks,
|
||||
IdAttribute: DefaultIdAttr,
|
||||
Prefix: DefaultPrefix,
|
||||
Canonicalizer: MakeC14N11Canonicalizer(),
|
||||
}
|
||||
}
|
||||
|
||||
func (ctx *SigningContext) SetSignatureMethod(algorithmID string) error {
|
||||
hash, ok := signatureMethodsByIdentifier[algorithmID]
|
||||
if !ok {
|
||||
return fmt.Errorf("Unknown SignatureMethod: %s", algorithmID)
|
||||
}
|
||||
|
||||
ctx.Hash = hash
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (ctx *SigningContext) digest(el *etree.Element) ([]byte, error) {
|
||||
canonical, err := ctx.Canonicalizer.Canonicalize(el)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
hash := ctx.Hash.New()
|
||||
_, err = hash.Write(canonical)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return hash.Sum(nil), nil
|
||||
}
|
||||
|
||||
func (ctx *SigningContext) constructSignedInfo(el *etree.Element, enveloped bool) (*etree.Element, error) {
|
||||
digestAlgorithmIdentifier := ctx.GetDigestAlgorithmIdentifier()
|
||||
if digestAlgorithmIdentifier == "" {
|
||||
return nil, errors.New("unsupported hash mechanism")
|
||||
}
|
||||
|
||||
signatureMethodIdentifier := ctx.GetSignatureMethodIdentifier()
|
||||
if signatureMethodIdentifier == "" {
|
||||
return nil, errors.New("unsupported signature method")
|
||||
}
|
||||
|
||||
digest, err := ctx.digest(el)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
signedInfo := &etree.Element{
|
||||
Tag: SignedInfoTag,
|
||||
Space: ctx.Prefix,
|
||||
}
|
||||
|
||||
// /SignedInfo/CanonicalizationMethod
|
||||
canonicalizationMethod := ctx.createNamespacedElement(signedInfo, CanonicalizationMethodTag)
|
||||
canonicalizationMethod.CreateAttr(AlgorithmAttr, string(ctx.Canonicalizer.Algorithm()))
|
||||
|
||||
// /SignedInfo/SignatureMethod
|
||||
signatureMethod := ctx.createNamespacedElement(signedInfo, SignatureMethodTag)
|
||||
signatureMethod.CreateAttr(AlgorithmAttr, signatureMethodIdentifier)
|
||||
|
||||
// /SignedInfo/Reference
|
||||
reference := ctx.createNamespacedElement(signedInfo, ReferenceTag)
|
||||
|
||||
dataId := el.SelectAttrValue(ctx.IdAttribute, "")
|
||||
if dataId == "" {
|
||||
return nil, errors.New("Missing data ID")
|
||||
}
|
||||
|
||||
reference.CreateAttr(URIAttr, "#"+dataId)
|
||||
|
||||
// /SignedInfo/Reference/Transforms
|
||||
transforms := ctx.createNamespacedElement(reference, TransformsTag)
|
||||
if enveloped {
|
||||
envelopedTransform := ctx.createNamespacedElement(transforms, TransformTag)
|
||||
envelopedTransform.CreateAttr(AlgorithmAttr, EnvelopedSignatureAltorithmId.String())
|
||||
}
|
||||
canonicalizationAlgorithm := ctx.createNamespacedElement(transforms, TransformTag)
|
||||
canonicalizationAlgorithm.CreateAttr(AlgorithmAttr, string(ctx.Canonicalizer.Algorithm()))
|
||||
|
||||
// /SignedInfo/Reference/DigestMethod
|
||||
digestMethod := ctx.createNamespacedElement(reference, DigestMethodTag)
|
||||
digestMethod.CreateAttr(AlgorithmAttr, digestAlgorithmIdentifier)
|
||||
|
||||
// /SignedInfo/Reference/DigestValue
|
||||
digestValue := ctx.createNamespacedElement(reference, DigestValueTag)
|
||||
digestValue.SetText(base64.StdEncoding.EncodeToString(digest))
|
||||
|
||||
return signedInfo, nil
|
||||
}
|
||||
|
||||
func (ctx *SigningContext) ConstructSignature(el *etree.Element, enveloped bool) (*etree.Element, error) {
|
||||
signedInfo, err := ctx.constructSignedInfo(el, enveloped)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
sig := &etree.Element{
|
||||
Tag: SignatureTag,
|
||||
Space: ctx.Prefix,
|
||||
}
|
||||
|
||||
xmlns := "xmlns"
|
||||
if ctx.Prefix != "" {
|
||||
xmlns += ":" + ctx.Prefix
|
||||
}
|
||||
|
||||
sig.CreateAttr(xmlns, Namespace)
|
||||
sig.AddChild(signedInfo)
|
||||
|
||||
// When using xml-c14n11 (ie, non-exclusive canonicalization) the canonical form
|
||||
// of the SignedInfo must declare all namespaces that are in scope at it's final
|
||||
// enveloped location in the document. In order to do that, we're going to construct
|
||||
// a series of cascading NSContexts to capture namespace declarations:
|
||||
|
||||
// First get the context surrounding the element we are signing.
|
||||
rootNSCtx, err := etreeutils.NSBuildParentContext(el)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Then capture any declarations on the element itself.
|
||||
elNSCtx, err := rootNSCtx.SubContext(el)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Followed by declarations on the Signature (which we just added above)
|
||||
sigNSCtx, err := elNSCtx.SubContext(sig)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Finally detatch the SignedInfo in order to capture all of the namespace
|
||||
// declarations in the scope we've constructed.
|
||||
detatchedSignedInfo, err := etreeutils.NSDetatch(sigNSCtx, signedInfo)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
digest, err := ctx.digest(detatchedSignedInfo)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
key, cert, err := ctx.KeyStore.GetKeyPair()
|
||||
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)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
signatureValue := ctx.createNamespacedElement(sig, SignatureValueTag)
|
||||
signatureValue.SetText(base64.StdEncoding.EncodeToString(rawSignature))
|
||||
|
||||
keyInfo := ctx.createNamespacedElement(sig, KeyInfoTag)
|
||||
x509Data := ctx.createNamespacedElement(keyInfo, X509DataTag)
|
||||
for _, cert := range certs {
|
||||
x509Certificate := ctx.createNamespacedElement(x509Data, X509CertificateTag)
|
||||
x509Certificate.SetText(base64.StdEncoding.EncodeToString(cert))
|
||||
}
|
||||
|
||||
return sig, nil
|
||||
}
|
||||
|
||||
func (ctx *SigningContext) createNamespacedElement(el *etree.Element, tag string) *etree.Element {
|
||||
child := el.CreateElement(tag)
|
||||
child.Space = ctx.Prefix
|
||||
return child
|
||||
}
|
||||
|
||||
func (ctx *SigningContext) SignEnveloped(el *etree.Element) (*etree.Element, error) {
|
||||
sig, err := ctx.ConstructSignature(el, true)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
ret := el.Copy()
|
||||
ret.Child = append(ret.Child, sig)
|
||||
|
||||
return ret, nil
|
||||
}
|
||||
|
||||
func (ctx *SigningContext) GetSignatureMethodIdentifier() string {
|
||||
if ident, ok := signatureMethodIdentifiers[ctx.Hash]; ok {
|
||||
return ident
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func (ctx *SigningContext) GetDigestAlgorithmIdentifier() string {
|
||||
if ident, ok := digestAlgorithmIdentifiers[ctx.Hash]; ok {
|
||||
return ident
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
// Useful for signing query string (including DEFLATED AuthnRequest) when
|
||||
// using HTTP-Redirect to make a signed request.
|
||||
// See 3.4.4.1 DEFLATE Encoding of https://docs.oasis-open.org/security/saml/v2.0/saml-bindings-2.0-os.pdf
|
||||
func (ctx *SigningContext) SignString(content string) ([]byte, error) {
|
||||
hash := ctx.Hash.New()
|
||||
if ln, err := hash.Write([]byte(content)); err != nil {
|
||||
return nil, fmt.Errorf("error calculating hash: %v", err)
|
||||
} else if ln < 1 {
|
||||
return nil, fmt.Errorf("zero length hash")
|
||||
}
|
||||
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
|
||||
}
|
39
vendor/github.com/russellhaering/goxmldsig/tls_keystore.go
generated
vendored
Normal file
39
vendor/github.com/russellhaering/goxmldsig/tls_keystore.go
generated
vendored
Normal file
@ -0,0 +1,39 @@
|
||||
package dsig
|
||||
|
||||
import (
|
||||
"crypto/rsa"
|
||||
"crypto/tls"
|
||||
"fmt"
|
||||
)
|
||||
|
||||
//Well-known errors
|
||||
var (
|
||||
ErrNonRSAKey = fmt.Errorf("Private key was not RSA")
|
||||
ErrMissingCertificates = fmt.Errorf("No public certificates provided")
|
||||
)
|
||||
|
||||
//TLSCertKeyStore wraps the stdlib tls.Certificate to return its contained key
|
||||
//and certs.
|
||||
type TLSCertKeyStore tls.Certificate
|
||||
|
||||
//GetKeyPair implements X509KeyStore using the underlying tls.Certificate
|
||||
func (d TLSCertKeyStore) GetKeyPair() (*rsa.PrivateKey, []byte, error) {
|
||||
pk, ok := d.PrivateKey.(*rsa.PrivateKey)
|
||||
|
||||
if !ok {
|
||||
return nil, nil, ErrNonRSAKey
|
||||
}
|
||||
|
||||
if len(d.Certificate) < 1 {
|
||||
return nil, nil, ErrMissingCertificates
|
||||
}
|
||||
|
||||
crt := d.Certificate[0]
|
||||
|
||||
return pk, crt, nil
|
||||
}
|
||||
|
||||
//GetChain impliments X509ChainStore using the underlying tls.Certificate
|
||||
func (d TLSCertKeyStore) GetChain() ([][]byte, error) {
|
||||
return d.Certificate, nil
|
||||
}
|
93
vendor/github.com/russellhaering/goxmldsig/types/signature.go
generated
vendored
Normal file
93
vendor/github.com/russellhaering/goxmldsig/types/signature.go
generated
vendored
Normal file
@ -0,0 +1,93 @@
|
||||
package types
|
||||
|
||||
import (
|
||||
"encoding/xml"
|
||||
|
||||
"github.com/beevik/etree"
|
||||
)
|
||||
|
||||
type InclusiveNamespaces struct {
|
||||
XMLName xml.Name `xml:"http://www.w3.org/2001/10/xml-exc-c14n# InclusiveNamespaces"`
|
||||
PrefixList string `xml:"PrefixList,attr"`
|
||||
}
|
||||
|
||||
type Transform struct {
|
||||
XMLName xml.Name `xml:"http://www.w3.org/2000/09/xmldsig# Transform"`
|
||||
Algorithm string `xml:"Algorithm,attr"`
|
||||
InclusiveNamespaces *InclusiveNamespaces `xml:"InclusiveNamespaces"`
|
||||
}
|
||||
|
||||
type Transforms struct {
|
||||
XMLName xml.Name `xml:"http://www.w3.org/2000/09/xmldsig# Transforms"`
|
||||
Transforms []Transform `xml:"Transform"`
|
||||
}
|
||||
|
||||
type DigestMethod struct {
|
||||
XMLName xml.Name `xml:"http://www.w3.org/2000/09/xmldsig# DigestMethod"`
|
||||
Algorithm string `xml:"Algorithm,attr"`
|
||||
}
|
||||
|
||||
type Reference struct {
|
||||
XMLName xml.Name `xml:"http://www.w3.org/2000/09/xmldsig# Reference"`
|
||||
URI string `xml:"URI,attr"`
|
||||
DigestValue string `xml:"DigestValue"`
|
||||
DigestAlgo DigestMethod `xml:"DigestMethod"`
|
||||
Transforms Transforms `xml:"Transforms"`
|
||||
}
|
||||
|
||||
type CanonicalizationMethod struct {
|
||||
XMLName xml.Name `xml:"http://www.w3.org/2000/09/xmldsig# CanonicalizationMethod"`
|
||||
Algorithm string `xml:"Algorithm,attr"`
|
||||
}
|
||||
|
||||
type SignatureMethod struct {
|
||||
XMLName xml.Name `xml:"http://www.w3.org/2000/09/xmldsig# SignatureMethod"`
|
||||
Algorithm string `xml:"Algorithm,attr"`
|
||||
}
|
||||
|
||||
type SignedInfo struct {
|
||||
XMLName xml.Name `xml:"http://www.w3.org/2000/09/xmldsig# SignedInfo"`
|
||||
CanonicalizationMethod CanonicalizationMethod `xml:"CanonicalizationMethod"`
|
||||
SignatureMethod SignatureMethod `xml:"SignatureMethod"`
|
||||
References []Reference `xml:"Reference"`
|
||||
}
|
||||
|
||||
type SignatureValue struct {
|
||||
XMLName xml.Name `xml:"http://www.w3.org/2000/09/xmldsig# SignatureValue"`
|
||||
Data string `xml:",chardata"`
|
||||
}
|
||||
|
||||
type KeyInfo struct {
|
||||
XMLName xml.Name `xml:"http://www.w3.org/2000/09/xmldsig# KeyInfo"`
|
||||
X509Data X509Data `xml:"X509Data"`
|
||||
}
|
||||
|
||||
type X509Data struct {
|
||||
XMLName xml.Name `xml:"http://www.w3.org/2000/09/xmldsig# X509Data"`
|
||||
X509Certificates []X509Certificate `xml:"X509Certificate"`
|
||||
}
|
||||
|
||||
type X509Certificate struct {
|
||||
XMLName xml.Name `xml:"http://www.w3.org/2000/09/xmldsig# X509Certificate"`
|
||||
Data string `xml:",chardata"`
|
||||
}
|
||||
|
||||
type Signature struct {
|
||||
XMLName xml.Name `xml:"http://www.w3.org/2000/09/xmldsig# Signature"`
|
||||
SignedInfo *SignedInfo `xml:"SignedInfo"`
|
||||
SignatureValue *SignatureValue `xml:"SignatureValue"`
|
||||
KeyInfo *KeyInfo `xml:"KeyInfo"`
|
||||
el *etree.Element
|
||||
}
|
||||
|
||||
// SetUnderlyingElement will be called with a reference to the Element this Signature
|
||||
// was unmarshaled from.
|
||||
func (s *Signature) SetUnderlyingElement(el *etree.Element) {
|
||||
s.el = el
|
||||
}
|
||||
|
||||
// UnderlyingElement returns a reference to the Element this signature was unmarshaled
|
||||
// from, where applicable.
|
||||
func (s *Signature) UnderlyingElement() *etree.Element {
|
||||
return s.el
|
||||
}
|
467
vendor/github.com/russellhaering/goxmldsig/validate.go
generated
vendored
Normal file
467
vendor/github.com/russellhaering/goxmldsig/validate.go
generated
vendored
Normal file
@ -0,0 +1,467 @@
|
||||
package dsig
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"crypto/rsa"
|
||||
"crypto/x509"
|
||||
"encoding/base64"
|
||||
"errors"
|
||||
"fmt"
|
||||
"regexp"
|
||||
|
||||
"github.com/beevik/etree"
|
||||
"github.com/russellhaering/goxmldsig/etreeutils"
|
||||
"github.com/russellhaering/goxmldsig/types"
|
||||
)
|
||||
|
||||
var uriRegexp = regexp.MustCompile("^#[a-zA-Z_][\\w.-]*$")
|
||||
var whiteSpace = regexp.MustCompile("\\s+")
|
||||
|
||||
var (
|
||||
// ErrMissingSignature indicates that no enveloped signature was found referencing
|
||||
// the top level element passed for signature verification.
|
||||
ErrMissingSignature = errors.New("Missing signature referencing the top-level element")
|
||||
)
|
||||
|
||||
type ValidationContext struct {
|
||||
CertificateStore X509CertificateStore
|
||||
IdAttribute string
|
||||
Clock *Clock
|
||||
}
|
||||
|
||||
func NewDefaultValidationContext(certificateStore X509CertificateStore) *ValidationContext {
|
||||
return &ValidationContext{
|
||||
CertificateStore: certificateStore,
|
||||
IdAttribute: DefaultIdAttr,
|
||||
}
|
||||
}
|
||||
|
||||
// TODO(russell_h): More flexible namespace support. This might barely work.
|
||||
func inNamespace(el *etree.Element, ns string) bool {
|
||||
for _, attr := range el.Attr {
|
||||
if attr.Value == ns {
|
||||
if attr.Space == "" && attr.Key == "xmlns" {
|
||||
return el.Space == ""
|
||||
} else if attr.Space == "xmlns" {
|
||||
return el.Space == attr.Key
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
func childPath(space, tag string) string {
|
||||
if space == "" {
|
||||
return "./" + tag
|
||||
} else {
|
||||
return "./" + space + ":" + tag
|
||||
}
|
||||
}
|
||||
|
||||
func mapPathToElement(tree, el *etree.Element) []int {
|
||||
for i, child := range tree.Child {
|
||||
if child == el {
|
||||
return []int{i}
|
||||
}
|
||||
}
|
||||
|
||||
for i, child := range tree.Child {
|
||||
if childElement, ok := child.(*etree.Element); ok {
|
||||
childPath := mapPathToElement(childElement, el)
|
||||
if childElement != nil {
|
||||
return append([]int{i}, childPath...)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func removeElementAtPath(el *etree.Element, path []int) bool {
|
||||
if len(path) == 0 {
|
||||
return false
|
||||
}
|
||||
|
||||
if len(el.Child) <= path[0] {
|
||||
return false
|
||||
}
|
||||
|
||||
childElement, ok := el.Child[path[0]].(*etree.Element)
|
||||
if !ok {
|
||||
return false
|
||||
}
|
||||
|
||||
if len(path) == 1 {
|
||||
el.RemoveChild(childElement)
|
||||
return true
|
||||
}
|
||||
|
||||
return removeElementAtPath(childElement, path[1:])
|
||||
}
|
||||
|
||||
// Transform returns a new element equivalent to the passed root el, but with
|
||||
// the set of transformations described by the ref applied.
|
||||
//
|
||||
// The functionality of transform is currently very limited and purpose-specific.
|
||||
func (ctx *ValidationContext) transform(
|
||||
el *etree.Element,
|
||||
sig *types.Signature,
|
||||
ref *types.Reference) (*etree.Element, Canonicalizer, error) {
|
||||
transforms := ref.Transforms.Transforms
|
||||
|
||||
if len(transforms) != 2 {
|
||||
return nil, nil, errors.New("Expected Enveloped and C14N transforms")
|
||||
}
|
||||
|
||||
// map the path to the passed signature relative to the passed root, in
|
||||
// order to enable removal of the signature by an enveloped signature
|
||||
// transform
|
||||
signaturePath := mapPathToElement(el, sig.UnderlyingElement())
|
||||
|
||||
// make a copy of the passed root
|
||||
el = el.Copy()
|
||||
|
||||
var canonicalizer Canonicalizer
|
||||
|
||||
for _, transform := range transforms {
|
||||
algo := transform.Algorithm
|
||||
|
||||
switch AlgorithmID(algo) {
|
||||
case EnvelopedSignatureAltorithmId:
|
||||
if !removeElementAtPath(el, signaturePath) {
|
||||
return nil, nil, errors.New("Error applying canonicalization transform: Signature not found")
|
||||
}
|
||||
|
||||
case CanonicalXML10ExclusiveAlgorithmId:
|
||||
var prefixList string
|
||||
if transform.InclusiveNamespaces != nil {
|
||||
prefixList = transform.InclusiveNamespaces.PrefixList
|
||||
}
|
||||
|
||||
canonicalizer = MakeC14N10ExclusiveCanonicalizerWithPrefixList(prefixList)
|
||||
|
||||
case CanonicalXML11AlgorithmId:
|
||||
canonicalizer = MakeC14N11Canonicalizer()
|
||||
|
||||
case CanonicalXML10RecAlgorithmId:
|
||||
canonicalizer = MakeC14N10RecCanonicalizer()
|
||||
|
||||
case CanonicalXML10CommentAlgorithmId:
|
||||
canonicalizer = MakeC14N10CommentCanonicalizer()
|
||||
|
||||
default:
|
||||
return nil, nil, errors.New("Unknown Transform Algorithm: " + algo)
|
||||
}
|
||||
}
|
||||
|
||||
if canonicalizer == nil {
|
||||
return nil, nil, errors.New("Expected canonicalization transform")
|
||||
}
|
||||
|
||||
return el, canonicalizer, nil
|
||||
}
|
||||
|
||||
func (ctx *ValidationContext) digest(el *etree.Element, digestAlgorithmId string, canonicalizer Canonicalizer) ([]byte, error) {
|
||||
data, err := canonicalizer.Canonicalize(el)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
digestAlgorithm, ok := digestAlgorithmsByIdentifier[digestAlgorithmId]
|
||||
if !ok {
|
||||
return nil, errors.New("Unknown digest algorithm: " + digestAlgorithmId)
|
||||
}
|
||||
|
||||
hash := digestAlgorithm.New()
|
||||
_, err = hash.Write(data)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return hash.Sum(nil), nil
|
||||
}
|
||||
|
||||
func (ctx *ValidationContext) verifySignedInfo(sig *types.Signature, canonicalizer Canonicalizer, signatureMethodId string, cert *x509.Certificate, decodedSignature []byte) error {
|
||||
signatureElement := sig.UnderlyingElement()
|
||||
|
||||
nsCtx, err := etreeutils.NSBuildParentContext(signatureElement)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
signedInfo, err := etreeutils.NSFindOneChildCtx(nsCtx, signatureElement, Namespace, SignedInfoTag)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if signedInfo == nil {
|
||||
return errors.New("Missing SignedInfo")
|
||||
}
|
||||
|
||||
// Canonicalize the xml
|
||||
canonical, err := canonicalSerialize(signedInfo)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
signatureAlgorithm, ok := signatureMethodsByIdentifier[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)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (ctx *ValidationContext) validateSignature(el *etree.Element, sig *types.Signature, cert *x509.Certificate) (*etree.Element, error) {
|
||||
idAttr := el.SelectAttr(ctx.IdAttribute)
|
||||
if idAttr == nil || idAttr.Value == "" {
|
||||
return nil, errors.New("Missing ID attribute")
|
||||
}
|
||||
|
||||
var ref *types.Reference
|
||||
|
||||
// Find the first reference which references the top-level element
|
||||
for _, _ref := range sig.SignedInfo.References {
|
||||
if _ref.URI == "" || _ref.URI[1:] == idAttr.Value {
|
||||
ref = &_ref
|
||||
}
|
||||
}
|
||||
|
||||
// Perform all transformations listed in the 'SignedInfo'
|
||||
// Basically, this means removing the 'SignedInfo'
|
||||
transformed, canonicalizer, err := ctx.transform(el, sig, ref)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
digestAlgorithm := ref.DigestAlgo.Algorithm
|
||||
|
||||
// Digest the transformed XML and compare it to the 'DigestValue' from the 'SignedInfo'
|
||||
digest, err := ctx.digest(transformed, digestAlgorithm, canonicalizer)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
decodedDigestValue, err := base64.StdEncoding.DecodeString(ref.DigestValue)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if !bytes.Equal(digest, decodedDigestValue) {
|
||||
return nil, errors.New("Signature could not be verified")
|
||||
}
|
||||
|
||||
// Decode the 'SignatureValue' so we can compare against it
|
||||
decodedSignature, err := base64.StdEncoding.DecodeString(sig.SignatureValue.Data)
|
||||
if err != nil {
|
||||
return nil, errors.New("Could not decode signature")
|
||||
}
|
||||
|
||||
// Actually verify the 'SignedInfo' was signed by a trusted source
|
||||
signatureMethod := sig.SignedInfo.SignatureMethod.Algorithm
|
||||
err = ctx.verifySignedInfo(sig, canonicalizer, signatureMethod, cert, decodedSignature)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return transformed, nil
|
||||
}
|
||||
|
||||
func contains(roots []*x509.Certificate, cert *x509.Certificate) bool {
|
||||
for _, root := range roots {
|
||||
if root.Equal(cert) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// findSignature searches for a Signature element referencing the passed root element.
|
||||
func (ctx *ValidationContext) findSignature(el *etree.Element) (*types.Signature, error) {
|
||||
idAttr := el.SelectAttr(ctx.IdAttribute)
|
||||
if idAttr == nil || idAttr.Value == "" {
|
||||
return nil, errors.New("Missing ID attribute")
|
||||
}
|
||||
|
||||
var sig *types.Signature
|
||||
|
||||
// Traverse the tree looking for a Signature element
|
||||
err := etreeutils.NSFindIterate(el, Namespace, SignatureTag, func(ctx etreeutils.NSContext, el *etree.Element) error {
|
||||
|
||||
found := false
|
||||
err := etreeutils.NSFindChildrenIterateCtx(ctx, el, Namespace, SignedInfoTag,
|
||||
func(ctx etreeutils.NSContext, signedInfo *etree.Element) error {
|
||||
detachedSignedInfo, err := etreeutils.NSDetatch(ctx, signedInfo)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
c14NMethod, err := etreeutils.NSFindOneChildCtx(ctx, detachedSignedInfo, Namespace, CanonicalizationMethodTag)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if c14NMethod == nil {
|
||||
return errors.New("missing CanonicalizationMethod on Signature")
|
||||
}
|
||||
|
||||
c14NAlgorithm := c14NMethod.SelectAttrValue(AlgorithmAttr, "")
|
||||
|
||||
var canonicalSignedInfo *etree.Element
|
||||
|
||||
switch AlgorithmID(c14NAlgorithm) {
|
||||
case CanonicalXML10ExclusiveAlgorithmId:
|
||||
err := etreeutils.TransformExcC14n(detachedSignedInfo, "")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// NOTE: TransformExcC14n transforms the element in-place,
|
||||
// while canonicalPrep isn't meant to. Once we standardize
|
||||
// this behavior we can drop this, as well as the adding and
|
||||
// removing of elements below.
|
||||
canonicalSignedInfo = detachedSignedInfo
|
||||
|
||||
case CanonicalXML11AlgorithmId:
|
||||
canonicalSignedInfo = canonicalPrep(detachedSignedInfo, map[string]struct{}{})
|
||||
|
||||
case CanonicalXML10RecAlgorithmId:
|
||||
canonicalSignedInfo = canonicalPrep(detachedSignedInfo, map[string]struct{}{})
|
||||
|
||||
case CanonicalXML10CommentAlgorithmId:
|
||||
canonicalSignedInfo = canonicalPrep(detachedSignedInfo, map[string]struct{}{})
|
||||
|
||||
default:
|
||||
return fmt.Errorf("invalid CanonicalizationMethod on Signature: %s", c14NAlgorithm)
|
||||
}
|
||||
|
||||
el.RemoveChild(signedInfo)
|
||||
el.AddChild(canonicalSignedInfo)
|
||||
|
||||
found = true
|
||||
|
||||
return etreeutils.ErrTraversalHalted
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if !found {
|
||||
return errors.New("Missing SignedInfo")
|
||||
}
|
||||
|
||||
// Unmarshal the signature into a structured Signature type
|
||||
_sig := &types.Signature{}
|
||||
err = etreeutils.NSUnmarshalElement(ctx, el, _sig)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Traverse references in the signature to determine whether it has at least
|
||||
// one reference to the top level element. If so, conclude the search.
|
||||
for _, ref := range _sig.SignedInfo.References {
|
||||
if ref.URI == "" || ref.URI[1:] == idAttr.Value {
|
||||
sig = _sig
|
||||
return etreeutils.ErrTraversalHalted
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if sig == nil {
|
||||
return nil, ErrMissingSignature
|
||||
}
|
||||
|
||||
return sig, nil
|
||||
}
|
||||
|
||||
func (ctx *ValidationContext) verifyCertificate(sig *types.Signature) (*x509.Certificate, error) {
|
||||
now := ctx.Clock.Now()
|
||||
|
||||
roots, err := ctx.CertificateStore.Certificates()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var cert *x509.Certificate
|
||||
|
||||
if sig.KeyInfo != nil {
|
||||
// If the Signature includes KeyInfo, extract the certificate from there
|
||||
if len(sig.KeyInfo.X509Data.X509Certificates) == 0 || sig.KeyInfo.X509Data.X509Certificates[0].Data == "" {
|
||||
return nil, errors.New("missing X509Certificate within KeyInfo")
|
||||
}
|
||||
|
||||
certData, err := base64.StdEncoding.DecodeString(
|
||||
whiteSpace.ReplaceAllString(sig.KeyInfo.X509Data.X509Certificates[0].Data, ""))
|
||||
if err != nil {
|
||||
return nil, errors.New("Failed to parse certificate")
|
||||
}
|
||||
|
||||
cert, err = x509.ParseCertificate(certData)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
} else {
|
||||
// If the Signature doesn't have KeyInfo, Use the root certificate if there is only one
|
||||
if len(roots) == 1 {
|
||||
cert = roots[0]
|
||||
} else {
|
||||
return nil, errors.New("Missing x509 Element")
|
||||
}
|
||||
}
|
||||
|
||||
// Verify that the certificate is one we trust
|
||||
if !contains(roots, cert) {
|
||||
return nil, errors.New("Could not verify certificate against trusted certs")
|
||||
}
|
||||
|
||||
if now.Before(cert.NotBefore) || now.After(cert.NotAfter) {
|
||||
return nil, errors.New("Cert is not valid at this time")
|
||||
}
|
||||
|
||||
return cert, nil
|
||||
}
|
||||
|
||||
// Validate verifies that the passed element contains a valid enveloped signature
|
||||
// matching a currently-valid certificate in the context's CertificateStore.
|
||||
func (ctx *ValidationContext) Validate(el *etree.Element) (*etree.Element, error) {
|
||||
// Make a copy of the element to avoid mutating the one we were passed.
|
||||
el = el.Copy()
|
||||
|
||||
sig, err := ctx.findSignature(el)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
cert, err := ctx.verifyCertificate(sig)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return ctx.validateSignature(el, sig, cert)
|
||||
}
|
81
vendor/github.com/russellhaering/goxmldsig/xml_constants.go
generated
vendored
Normal file
81
vendor/github.com/russellhaering/goxmldsig/xml_constants.go
generated
vendored
Normal file
@ -0,0 +1,81 @@
|
||||
package dsig
|
||||
|
||||
import "crypto"
|
||||
|
||||
const (
|
||||
DefaultPrefix = "ds"
|
||||
Namespace = "http://www.w3.org/2000/09/xmldsig#"
|
||||
)
|
||||
|
||||
// Tags
|
||||
const (
|
||||
SignatureTag = "Signature"
|
||||
SignedInfoTag = "SignedInfo"
|
||||
CanonicalizationMethodTag = "CanonicalizationMethod"
|
||||
SignatureMethodTag = "SignatureMethod"
|
||||
ReferenceTag = "Reference"
|
||||
TransformsTag = "Transforms"
|
||||
TransformTag = "Transform"
|
||||
DigestMethodTag = "DigestMethod"
|
||||
DigestValueTag = "DigestValue"
|
||||
SignatureValueTag = "SignatureValue"
|
||||
KeyInfoTag = "KeyInfo"
|
||||
X509DataTag = "X509Data"
|
||||
X509CertificateTag = "X509Certificate"
|
||||
InclusiveNamespacesTag = "InclusiveNamespaces"
|
||||
)
|
||||
|
||||
const (
|
||||
AlgorithmAttr = "Algorithm"
|
||||
URIAttr = "URI"
|
||||
DefaultIdAttr = "ID"
|
||||
PrefixListAttr = "PrefixList"
|
||||
)
|
||||
|
||||
type AlgorithmID string
|
||||
|
||||
func (id AlgorithmID) String() string {
|
||||
return string(id)
|
||||
}
|
||||
|
||||
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"
|
||||
)
|
||||
|
||||
//Well-known signature algorithms
|
||||
const (
|
||||
// Supported canonicalization algorithms
|
||||
CanonicalXML10ExclusiveAlgorithmId AlgorithmID = "http://www.w3.org/2001/10/xml-exc-c14n#"
|
||||
CanonicalXML11AlgorithmId AlgorithmID = "http://www.w3.org/2006/12/xml-c14n11"
|
||||
|
||||
CanonicalXML10RecAlgorithmId AlgorithmID = "http://www.w3.org/TR/2001/REC-xml-c14n-20010315"
|
||||
CanonicalXML10CommentAlgorithmId AlgorithmID = "http://www.w3.org/TR/2001/REC-xml-c14n-20010315#WithComments"
|
||||
|
||||
EnvelopedSignatureAltorithmId AlgorithmID = "http://www.w3.org/2000/09/xmldsig#enveloped-signature"
|
||||
)
|
||||
|
||||
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.SHA512: "http://www.w3.org/2001/04/xmlenc#sha512",
|
||||
}
|
||||
|
||||
var digestAlgorithmsByIdentifier = map[string]crypto.Hash{}
|
||||
var signatureMethodsByIdentifier = map[string]crypto.Hash{}
|
||||
|
||||
func init() {
|
||||
for hash, id := range digestAlgorithmIdentifiers {
|
||||
digestAlgorithmsByIdentifier[id] = hash
|
||||
}
|
||||
for hash, id := range signatureMethodIdentifiers {
|
||||
signatureMethodsByIdentifier[id] = hash
|
||||
}
|
||||
}
|
||||
|
||||
var signatureMethodIdentifiers = map[crypto.Hash]string{
|
||||
crypto.SHA1: RSASHA1SignatureMethod,
|
||||
crypto.SHA256: RSASHA256SignatureMethod,
|
||||
crypto.SHA512: RSASHA512SignatureMethod,
|
||||
}
|
28
vendor/github.com/stretchr/testify/require/doc.go
generated
vendored
Normal file
28
vendor/github.com/stretchr/testify/require/doc.go
generated
vendored
Normal file
@ -0,0 +1,28 @@
|
||||
// Package require implements the same assertions as the `assert` package but
|
||||
// stops test execution when a test fails.
|
||||
//
|
||||
// Example Usage
|
||||
//
|
||||
// The following is a complete example using require in a standard test function:
|
||||
// import (
|
||||
// "testing"
|
||||
// "github.com/stretchr/testify/require"
|
||||
// )
|
||||
//
|
||||
// func TestSomething(t *testing.T) {
|
||||
//
|
||||
// var a string = "Hello"
|
||||
// var b string = "Hello"
|
||||
//
|
||||
// require.Equal(t, a, b, "The two words should be the same.")
|
||||
//
|
||||
// }
|
||||
//
|
||||
// Assertions
|
||||
//
|
||||
// The `require` package have same global functions as in the `assert` package,
|
||||
// but instead of returning a boolean result they call `t.FailNow()`.
|
||||
//
|
||||
// Every assertion function also takes an optional string message as the final argument,
|
||||
// allowing custom error messages to be appended to the message the assertion method outputs.
|
||||
package require
|
16
vendor/github.com/stretchr/testify/require/forward_requirements.go
generated
vendored
Normal file
16
vendor/github.com/stretchr/testify/require/forward_requirements.go
generated
vendored
Normal file
@ -0,0 +1,16 @@
|
||||
package require
|
||||
|
||||
// Assertions provides assertion methods around the
|
||||
// TestingT interface.
|
||||
type Assertions struct {
|
||||
t TestingT
|
||||
}
|
||||
|
||||
// New makes a new Assertions object for the specified TestingT.
|
||||
func New(t TestingT) *Assertions {
|
||||
return &Assertions{
|
||||
t: t,
|
||||
}
|
||||
}
|
||||
|
||||
//go:generate go run ../_codegen/main.go -output-package=require -template=require_forward.go.tmpl -include-format-funcs
|
1227
vendor/github.com/stretchr/testify/require/require.go
generated
vendored
Normal file
1227
vendor/github.com/stretchr/testify/require/require.go
generated
vendored
Normal file
File diff suppressed because it is too large
Load Diff
6
vendor/github.com/stretchr/testify/require/require.go.tmpl
generated
vendored
Normal file
6
vendor/github.com/stretchr/testify/require/require.go.tmpl
generated
vendored
Normal file
@ -0,0 +1,6 @@
|
||||
{{.Comment}}
|
||||
func {{.DocInfo.Name}}(t TestingT, {{.Params}}) {
|
||||
if assert.{{.DocInfo.Name}}(t, {{.ForwardedParams}}) { return }
|
||||
if h, ok := t.(tHelper); ok { h.Helper() }
|
||||
t.FailNow()
|
||||
}
|
957
vendor/github.com/stretchr/testify/require/require_forward.go
generated
vendored
Normal file
957
vendor/github.com/stretchr/testify/require/require_forward.go
generated
vendored
Normal file
@ -0,0 +1,957 @@
|
||||
/*
|
||||
* CODE GENERATED AUTOMATICALLY WITH github.com/stretchr/testify/_codegen
|
||||
* THIS FILE MUST NOT BE EDITED BY HAND
|
||||
*/
|
||||
|
||||
package require
|
||||
|
||||
import (
|
||||
assert "github.com/stretchr/testify/assert"
|
||||
http "net/http"
|
||||
url "net/url"
|
||||
time "time"
|
||||
)
|
||||
|
||||
// Condition uses a Comparison to assert a complex condition.
|
||||
func (a *Assertions) Condition(comp assert.Comparison, msgAndArgs ...interface{}) {
|
||||
if h, ok := a.t.(tHelper); ok {
|
||||
h.Helper()
|
||||
}
|
||||
Condition(a.t, comp, msgAndArgs...)
|
||||
}
|
||||
|
||||
// Conditionf uses a Comparison to assert a complex condition.
|
||||
func (a *Assertions) Conditionf(comp assert.Comparison, msg string, args ...interface{}) {
|
||||
if h, ok := a.t.(tHelper); ok {
|
||||
h.Helper()
|
||||
}
|
||||
Conditionf(a.t, comp, msg, args...)
|
||||
}
|
||||
|
||||
// Contains asserts that the specified string, list(array, slice...) or map contains the
|
||||
// specified substring or element.
|
||||
//
|
||||
// a.Contains("Hello World", "World")
|
||||
// a.Contains(["Hello", "World"], "World")
|
||||
// a.Contains({"Hello": "World"}, "Hello")
|
||||
func (a *Assertions) Contains(s interface{}, contains interface{}, msgAndArgs ...interface{}) {
|
||||
if h, ok := a.t.(tHelper); ok {
|
||||
h.Helper()
|
||||
}
|
||||
Contains(a.t, s, contains, msgAndArgs...)
|
||||
}
|
||||
|
||||
// Containsf asserts that the specified string, list(array, slice...) or map contains the
|
||||
// specified substring or element.
|
||||
//
|
||||
// a.Containsf("Hello World", "World", "error message %s", "formatted")
|
||||
// a.Containsf(["Hello", "World"], "World", "error message %s", "formatted")
|
||||
// a.Containsf({"Hello": "World"}, "Hello", "error message %s", "formatted")
|
||||
func (a *Assertions) Containsf(s interface{}, contains interface{}, msg string, args ...interface{}) {
|
||||
if h, ok := a.t.(tHelper); ok {
|
||||
h.Helper()
|
||||
}
|
||||
Containsf(a.t, s, contains, msg, args...)
|
||||
}
|
||||
|
||||
// DirExists checks whether a directory exists in the given path. It also fails if the path is a file rather a directory or there is an error checking whether it exists.
|
||||
func (a *Assertions) DirExists(path string, msgAndArgs ...interface{}) {
|
||||
if h, ok := a.t.(tHelper); ok {
|
||||
h.Helper()
|
||||
}
|
||||
DirExists(a.t, path, msgAndArgs...)
|
||||
}
|
||||
|
||||
// DirExistsf checks whether a directory exists in the given path. It also fails if the path is a file rather a directory or there is an error checking whether it exists.
|
||||
func (a *Assertions) DirExistsf(path string, msg string, args ...interface{}) {
|
||||
if h, ok := a.t.(tHelper); ok {
|
||||
h.Helper()
|
||||
}
|
||||
DirExistsf(a.t, path, msg, args...)
|
||||
}
|
||||
|
||||
// ElementsMatch asserts that the specified listA(array, slice...) is equal to specified
|
||||
// listB(array, slice...) ignoring the order of the elements. If there are duplicate elements,
|
||||
// the number of appearances of each of them in both lists should match.
|
||||
//
|
||||
// a.ElementsMatch([1, 3, 2, 3], [1, 3, 3, 2])
|
||||
func (a *Assertions) ElementsMatch(listA interface{}, listB interface{}, msgAndArgs ...interface{}) {
|
||||
if h, ok := a.t.(tHelper); ok {
|
||||
h.Helper()
|
||||
}
|
||||
ElementsMatch(a.t, listA, listB, msgAndArgs...)
|
||||
}
|
||||
|
||||
// ElementsMatchf asserts that the specified listA(array, slice...) is equal to specified
|
||||
// listB(array, slice...) ignoring the order of the elements. If there are duplicate elements,
|
||||
// the number of appearances of each of them in both lists should match.
|
||||
//
|
||||
// a.ElementsMatchf([1, 3, 2, 3], [1, 3, 3, 2], "error message %s", "formatted")
|
||||
func (a *Assertions) ElementsMatchf(listA interface{}, listB interface{}, msg string, args ...interface{}) {
|
||||
if h, ok := a.t.(tHelper); ok {
|
||||
h.Helper()
|
||||
}
|
||||
ElementsMatchf(a.t, listA, listB, msg, args...)
|
||||
}
|
||||
|
||||
// Empty asserts that the specified object is empty. I.e. nil, "", false, 0 or either
|
||||
// a slice or a channel with len == 0.
|
||||
//
|
||||
// a.Empty(obj)
|
||||
func (a *Assertions) Empty(object interface{}, msgAndArgs ...interface{}) {
|
||||
if h, ok := a.t.(tHelper); ok {
|
||||
h.Helper()
|
||||
}
|
||||
Empty(a.t, object, msgAndArgs...)
|
||||
}
|
||||
|
||||
// Emptyf asserts that the specified object is empty. I.e. nil, "", false, 0 or either
|
||||
// a slice or a channel with len == 0.
|
||||
//
|
||||
// a.Emptyf(obj, "error message %s", "formatted")
|
||||
func (a *Assertions) Emptyf(object interface{}, msg string, args ...interface{}) {
|
||||
if h, ok := a.t.(tHelper); ok {
|
||||
h.Helper()
|
||||
}
|
||||
Emptyf(a.t, object, msg, args...)
|
||||
}
|
||||
|
||||
// Equal asserts that two objects are equal.
|
||||
//
|
||||
// a.Equal(123, 123)
|
||||
//
|
||||
// Pointer variable equality is determined based on the equality of the
|
||||
// referenced values (as opposed to the memory addresses). Function equality
|
||||
// cannot be determined and will always fail.
|
||||
func (a *Assertions) Equal(expected interface{}, actual interface{}, msgAndArgs ...interface{}) {
|
||||
if h, ok := a.t.(tHelper); ok {
|
||||
h.Helper()
|
||||
}
|
||||
Equal(a.t, expected, actual, msgAndArgs...)
|
||||
}
|
||||
|
||||
// EqualError asserts that a function returned an error (i.e. not `nil`)
|
||||
// and that it is equal to the provided error.
|
||||
//
|
||||
// actualObj, err := SomeFunction()
|
||||
// a.EqualError(err, expectedErrorString)
|
||||
func (a *Assertions) EqualError(theError error, errString string, msgAndArgs ...interface{}) {
|
||||
if h, ok := a.t.(tHelper); ok {
|
||||
h.Helper()
|
||||
}
|
||||
EqualError(a.t, theError, errString, msgAndArgs...)
|
||||
}
|
||||
|
||||
// EqualErrorf asserts that a function returned an error (i.e. not `nil`)
|
||||
// and that it is equal to the provided error.
|
||||
//
|
||||
// actualObj, err := SomeFunction()
|
||||
// a.EqualErrorf(err, expectedErrorString, "error message %s", "formatted")
|
||||
func (a *Assertions) EqualErrorf(theError error, errString string, msg string, args ...interface{}) {
|
||||
if h, ok := a.t.(tHelper); ok {
|
||||
h.Helper()
|
||||
}
|
||||
EqualErrorf(a.t, theError, errString, msg, args...)
|
||||
}
|
||||
|
||||
// EqualValues asserts that two objects are equal or convertable to the same types
|
||||
// and equal.
|
||||
//
|
||||
// a.EqualValues(uint32(123), int32(123))
|
||||
func (a *Assertions) EqualValues(expected interface{}, actual interface{}, msgAndArgs ...interface{}) {
|
||||
if h, ok := a.t.(tHelper); ok {
|
||||
h.Helper()
|
||||
}
|
||||
EqualValues(a.t, expected, actual, msgAndArgs...)
|
||||
}
|
||||
|
||||
// EqualValuesf asserts that two objects are equal or convertable to the same types
|
||||
// and equal.
|
||||
//
|
||||
// a.EqualValuesf(uint32(123, "error message %s", "formatted"), int32(123))
|
||||
func (a *Assertions) EqualValuesf(expected interface{}, actual interface{}, msg string, args ...interface{}) {
|
||||
if h, ok := a.t.(tHelper); ok {
|
||||
h.Helper()
|
||||
}
|
||||
EqualValuesf(a.t, expected, actual, msg, args...)
|
||||
}
|
||||
|
||||
// Equalf asserts that two objects are equal.
|
||||
//
|
||||
// a.Equalf(123, 123, "error message %s", "formatted")
|
||||
//
|
||||
// Pointer variable equality is determined based on the equality of the
|
||||
// referenced values (as opposed to the memory addresses). Function equality
|
||||
// cannot be determined and will always fail.
|
||||
func (a *Assertions) Equalf(expected interface{}, actual interface{}, msg string, args ...interface{}) {
|
||||
if h, ok := a.t.(tHelper); ok {
|
||||
h.Helper()
|
||||
}
|
||||
Equalf(a.t, expected, actual, msg, args...)
|
||||
}
|
||||
|
||||
// Error asserts that a function returned an error (i.e. not `nil`).
|
||||
//
|
||||
// actualObj, err := SomeFunction()
|
||||
// if a.Error(err) {
|
||||
// assert.Equal(t, expectedError, err)
|
||||
// }
|
||||
func (a *Assertions) Error(err error, msgAndArgs ...interface{}) {
|
||||
if h, ok := a.t.(tHelper); ok {
|
||||
h.Helper()
|
||||
}
|
||||
Error(a.t, err, msgAndArgs...)
|
||||
}
|
||||
|
||||
// Errorf asserts that a function returned an error (i.e. not `nil`).
|
||||
//
|
||||
// actualObj, err := SomeFunction()
|
||||
// if a.Errorf(err, "error message %s", "formatted") {
|
||||
// assert.Equal(t, expectedErrorf, err)
|
||||
// }
|
||||
func (a *Assertions) Errorf(err error, msg string, args ...interface{}) {
|
||||
if h, ok := a.t.(tHelper); ok {
|
||||
h.Helper()
|
||||
}
|
||||
Errorf(a.t, err, msg, args...)
|
||||
}
|
||||
|
||||
// Exactly asserts that two objects are equal in value and type.
|
||||
//
|
||||
// a.Exactly(int32(123), int64(123))
|
||||
func (a *Assertions) Exactly(expected interface{}, actual interface{}, msgAndArgs ...interface{}) {
|
||||
if h, ok := a.t.(tHelper); ok {
|
||||
h.Helper()
|
||||
}
|
||||
Exactly(a.t, expected, actual, msgAndArgs...)
|
||||
}
|
||||
|
||||
// Exactlyf asserts that two objects are equal in value and type.
|
||||
//
|
||||
// a.Exactlyf(int32(123, "error message %s", "formatted"), int64(123))
|
||||
func (a *Assertions) Exactlyf(expected interface{}, actual interface{}, msg string, args ...interface{}) {
|
||||
if h, ok := a.t.(tHelper); ok {
|
||||
h.Helper()
|
||||
}
|
||||
Exactlyf(a.t, expected, actual, msg, args...)
|
||||
}
|
||||
|
||||
// Fail reports a failure through
|
||||
func (a *Assertions) Fail(failureMessage string, msgAndArgs ...interface{}) {
|
||||
if h, ok := a.t.(tHelper); ok {
|
||||
h.Helper()
|
||||
}
|
||||
Fail(a.t, failureMessage, msgAndArgs...)
|
||||
}
|
||||
|
||||
// FailNow fails test
|
||||
func (a *Assertions) FailNow(failureMessage string, msgAndArgs ...interface{}) {
|
||||
if h, ok := a.t.(tHelper); ok {
|
||||
h.Helper()
|
||||
}
|
||||
FailNow(a.t, failureMessage, msgAndArgs...)
|
||||
}
|
||||
|
||||
// FailNowf fails test
|
||||
func (a *Assertions) FailNowf(failureMessage string, msg string, args ...interface{}) {
|
||||
if h, ok := a.t.(tHelper); ok {
|
||||
h.Helper()
|
||||
}
|
||||
FailNowf(a.t, failureMessage, msg, args...)
|
||||
}
|
||||
|
||||
// Failf reports a failure through
|
||||
func (a *Assertions) Failf(failureMessage string, msg string, args ...interface{}) {
|
||||
if h, ok := a.t.(tHelper); ok {
|
||||
h.Helper()
|
||||
}
|
||||
Failf(a.t, failureMessage, msg, args...)
|
||||
}
|
||||
|
||||
// False asserts that the specified value is false.
|
||||
//
|
||||
// a.False(myBool)
|
||||
func (a *Assertions) False(value bool, msgAndArgs ...interface{}) {
|
||||
if h, ok := a.t.(tHelper); ok {
|
||||
h.Helper()
|
||||
}
|
||||
False(a.t, value, msgAndArgs...)
|
||||
}
|
||||
|
||||
// Falsef asserts that the specified value is false.
|
||||
//
|
||||
// a.Falsef(myBool, "error message %s", "formatted")
|
||||
func (a *Assertions) Falsef(value bool, msg string, args ...interface{}) {
|
||||
if h, ok := a.t.(tHelper); ok {
|
||||
h.Helper()
|
||||
}
|
||||
Falsef(a.t, value, msg, args...)
|
||||
}
|
||||
|
||||
// FileExists checks whether a file exists in the given path. It also fails if the path points to a directory or there is an error when trying to check the file.
|
||||
func (a *Assertions) FileExists(path string, msgAndArgs ...interface{}) {
|
||||
if h, ok := a.t.(tHelper); ok {
|
||||
h.Helper()
|
||||
}
|
||||
FileExists(a.t, path, msgAndArgs...)
|
||||
}
|
||||
|
||||
// FileExistsf checks whether a file exists in the given path. It also fails if the path points to a directory or there is an error when trying to check the file.
|
||||
func (a *Assertions) FileExistsf(path string, msg string, args ...interface{}) {
|
||||
if h, ok := a.t.(tHelper); ok {
|
||||
h.Helper()
|
||||
}
|
||||
FileExistsf(a.t, path, msg, args...)
|
||||
}
|
||||
|
||||
// HTTPBodyContains asserts that a specified handler returns a
|
||||
// body that contains a string.
|
||||
//
|
||||
// a.HTTPBodyContains(myHandler, "GET", "www.google.com", nil, "I'm Feeling Lucky")
|
||||
//
|
||||
// Returns whether the assertion was successful (true) or not (false).
|
||||
func (a *Assertions) HTTPBodyContains(handler http.HandlerFunc, method string, url string, values url.Values, str interface{}, msgAndArgs ...interface{}) {
|
||||
if h, ok := a.t.(tHelper); ok {
|
||||
h.Helper()
|
||||
}
|
||||
HTTPBodyContains(a.t, handler, method, url, values, str, msgAndArgs...)
|
||||
}
|
||||
|
||||
// HTTPBodyContainsf asserts that a specified handler returns a
|
||||
// body that contains a string.
|
||||
//
|
||||
// a.HTTPBodyContainsf(myHandler, "GET", "www.google.com", nil, "I'm Feeling Lucky", "error message %s", "formatted")
|
||||
//
|
||||
// Returns whether the assertion was successful (true) or not (false).
|
||||
func (a *Assertions) HTTPBodyContainsf(handler http.HandlerFunc, method string, url string, values url.Values, str interface{}, msg string, args ...interface{}) {
|
||||
if h, ok := a.t.(tHelper); ok {
|
||||
h.Helper()
|
||||
}
|
||||
HTTPBodyContainsf(a.t, handler, method, url, values, str, msg, args...)
|
||||
}
|
||||
|
||||
// HTTPBodyNotContains asserts that a specified handler returns a
|
||||
// body that does not contain a string.
|
||||
//
|
||||
// a.HTTPBodyNotContains(myHandler, "GET", "www.google.com", nil, "I'm Feeling Lucky")
|
||||
//
|
||||
// Returns whether the assertion was successful (true) or not (false).
|
||||
func (a *Assertions) HTTPBodyNotContains(handler http.HandlerFunc, method string, url string, values url.Values, str interface{}, msgAndArgs ...interface{}) {
|
||||
if h, ok := a.t.(tHelper); ok {
|
||||
h.Helper()
|
||||
}
|
||||
HTTPBodyNotContains(a.t, handler, method, url, values, str, msgAndArgs...)
|
||||
}
|
||||
|
||||
// HTTPBodyNotContainsf asserts that a specified handler returns a
|
||||
// body that does not contain a string.
|
||||
//
|
||||
// a.HTTPBodyNotContainsf(myHandler, "GET", "www.google.com", nil, "I'm Feeling Lucky", "error message %s", "formatted")
|
||||
//
|
||||
// Returns whether the assertion was successful (true) or not (false).
|
||||
func (a *Assertions) HTTPBodyNotContainsf(handler http.HandlerFunc, method string, url string, values url.Values, str interface{}, msg string, args ...interface{}) {
|
||||
if h, ok := a.t.(tHelper); ok {
|
||||
h.Helper()
|
||||
}
|
||||
HTTPBodyNotContainsf(a.t, handler, method, url, values, str, msg, args...)
|
||||
}
|
||||
|
||||
// HTTPError asserts that a specified handler returns an error status code.
|
||||
//
|
||||
// a.HTTPError(myHandler, "POST", "/a/b/c", url.Values{"a": []string{"b", "c"}}
|
||||
//
|
||||
// Returns whether the assertion was successful (true) or not (false).
|
||||
func (a *Assertions) HTTPError(handler http.HandlerFunc, method string, url string, values url.Values, msgAndArgs ...interface{}) {
|
||||
if h, ok := a.t.(tHelper); ok {
|
||||
h.Helper()
|
||||
}
|
||||
HTTPError(a.t, handler, method, url, values, msgAndArgs...)
|
||||
}
|
||||
|
||||
// HTTPErrorf asserts that a specified handler returns an error status code.
|
||||
//
|
||||
// a.HTTPErrorf(myHandler, "POST", "/a/b/c", url.Values{"a": []string{"b", "c"}}
|
||||
//
|
||||
// Returns whether the assertion was successful (true, "error message %s", "formatted") or not (false).
|
||||
func (a *Assertions) HTTPErrorf(handler http.HandlerFunc, method string, url string, values url.Values, msg string, args ...interface{}) {
|
||||
if h, ok := a.t.(tHelper); ok {
|
||||
h.Helper()
|
||||
}
|
||||
HTTPErrorf(a.t, handler, method, url, values, msg, args...)
|
||||
}
|
||||
|
||||
// HTTPRedirect asserts that a specified handler returns a redirect status code.
|
||||
//
|
||||
// a.HTTPRedirect(myHandler, "GET", "/a/b/c", url.Values{"a": []string{"b", "c"}}
|
||||
//
|
||||
// Returns whether the assertion was successful (true) or not (false).
|
||||
func (a *Assertions) HTTPRedirect(handler http.HandlerFunc, method string, url string, values url.Values, msgAndArgs ...interface{}) {
|
||||
if h, ok := a.t.(tHelper); ok {
|
||||
h.Helper()
|
||||
}
|
||||
HTTPRedirect(a.t, handler, method, url, values, msgAndArgs...)
|
||||
}
|
||||
|
||||
// HTTPRedirectf asserts that a specified handler returns a redirect status code.
|
||||
//
|
||||
// a.HTTPRedirectf(myHandler, "GET", "/a/b/c", url.Values{"a": []string{"b", "c"}}
|
||||
//
|
||||
// Returns whether the assertion was successful (true, "error message %s", "formatted") or not (false).
|
||||
func (a *Assertions) HTTPRedirectf(handler http.HandlerFunc, method string, url string, values url.Values, msg string, args ...interface{}) {
|
||||
if h, ok := a.t.(tHelper); ok {
|
||||
h.Helper()
|
||||
}
|
||||
HTTPRedirectf(a.t, handler, method, url, values, msg, args...)
|
||||
}
|
||||
|
||||
// HTTPSuccess asserts that a specified handler returns a success status code.
|
||||
//
|
||||
// a.HTTPSuccess(myHandler, "POST", "http://www.google.com", nil)
|
||||
//
|
||||
// Returns whether the assertion was successful (true) or not (false).
|
||||
func (a *Assertions) HTTPSuccess(handler http.HandlerFunc, method string, url string, values url.Values, msgAndArgs ...interface{}) {
|
||||
if h, ok := a.t.(tHelper); ok {
|
||||
h.Helper()
|
||||
}
|
||||
HTTPSuccess(a.t, handler, method, url, values, msgAndArgs...)
|
||||
}
|
||||
|
||||
// HTTPSuccessf asserts that a specified handler returns a success status code.
|
||||
//
|
||||
// a.HTTPSuccessf(myHandler, "POST", "http://www.google.com", nil, "error message %s", "formatted")
|
||||
//
|
||||
// Returns whether the assertion was successful (true) or not (false).
|
||||
func (a *Assertions) HTTPSuccessf(handler http.HandlerFunc, method string, url string, values url.Values, msg string, args ...interface{}) {
|
||||
if h, ok := a.t.(tHelper); ok {
|
||||
h.Helper()
|
||||
}
|
||||
HTTPSuccessf(a.t, handler, method, url, values, msg, args...)
|
||||
}
|
||||
|
||||
// Implements asserts that an object is implemented by the specified interface.
|
||||
//
|
||||
// a.Implements((*MyInterface)(nil), new(MyObject))
|
||||
func (a *Assertions) Implements(interfaceObject interface{}, object interface{}, msgAndArgs ...interface{}) {
|
||||
if h, ok := a.t.(tHelper); ok {
|
||||
h.Helper()
|
||||
}
|
||||
Implements(a.t, interfaceObject, object, msgAndArgs...)
|
||||
}
|
||||
|
||||
// Implementsf asserts that an object is implemented by the specified interface.
|
||||
//
|
||||
// a.Implementsf((*MyInterface, "error message %s", "formatted")(nil), new(MyObject))
|
||||
func (a *Assertions) Implementsf(interfaceObject interface{}, object interface{}, msg string, args ...interface{}) {
|
||||
if h, ok := a.t.(tHelper); ok {
|
||||
h.Helper()
|
||||
}
|
||||
Implementsf(a.t, interfaceObject, object, msg, args...)
|
||||
}
|
||||
|
||||
// InDelta asserts that the two numerals are within delta of each other.
|
||||
//
|
||||
// a.InDelta(math.Pi, (22 / 7.0), 0.01)
|
||||
func (a *Assertions) InDelta(expected interface{}, actual interface{}, delta float64, msgAndArgs ...interface{}) {
|
||||
if h, ok := a.t.(tHelper); ok {
|
||||
h.Helper()
|
||||
}
|
||||
InDelta(a.t, expected, actual, delta, msgAndArgs...)
|
||||
}
|
||||
|
||||
// InDeltaMapValues is the same as InDelta, but it compares all values between two maps. Both maps must have exactly the same keys.
|
||||
func (a *Assertions) InDeltaMapValues(expected interface{}, actual interface{}, delta float64, msgAndArgs ...interface{}) {
|
||||
if h, ok := a.t.(tHelper); ok {
|
||||
h.Helper()
|
||||
}
|
||||
InDeltaMapValues(a.t, expected, actual, delta, msgAndArgs...)
|
||||
}
|
||||
|
||||
// InDeltaMapValuesf is the same as InDelta, but it compares all values between two maps. Both maps must have exactly the same keys.
|
||||
func (a *Assertions) InDeltaMapValuesf(expected interface{}, actual interface{}, delta float64, msg string, args ...interface{}) {
|
||||
if h, ok := a.t.(tHelper); ok {
|
||||
h.Helper()
|
||||
}
|
||||
InDeltaMapValuesf(a.t, expected, actual, delta, msg, args...)
|
||||
}
|
||||
|
||||
// InDeltaSlice is the same as InDelta, except it compares two slices.
|
||||
func (a *Assertions) InDeltaSlice(expected interface{}, actual interface{}, delta float64, msgAndArgs ...interface{}) {
|
||||
if h, ok := a.t.(tHelper); ok {
|
||||
h.Helper()
|
||||
}
|
||||
InDeltaSlice(a.t, expected, actual, delta, msgAndArgs...)
|
||||
}
|
||||
|
||||
// InDeltaSlicef is the same as InDelta, except it compares two slices.
|
||||
func (a *Assertions) InDeltaSlicef(expected interface{}, actual interface{}, delta float64, msg string, args ...interface{}) {
|
||||
if h, ok := a.t.(tHelper); ok {
|
||||
h.Helper()
|
||||
}
|
||||
InDeltaSlicef(a.t, expected, actual, delta, msg, args...)
|
||||
}
|
||||
|
||||
// InDeltaf asserts that the two numerals are within delta of each other.
|
||||
//
|
||||
// a.InDeltaf(math.Pi, (22 / 7.0, "error message %s", "formatted"), 0.01)
|
||||
func (a *Assertions) InDeltaf(expected interface{}, actual interface{}, delta float64, msg string, args ...interface{}) {
|
||||
if h, ok := a.t.(tHelper); ok {
|
||||
h.Helper()
|
||||
}
|
||||
InDeltaf(a.t, expected, actual, delta, msg, args...)
|
||||
}
|
||||
|
||||
// InEpsilon asserts that expected and actual have a relative error less than epsilon
|
||||
func (a *Assertions) InEpsilon(expected interface{}, actual interface{}, epsilon float64, msgAndArgs ...interface{}) {
|
||||
if h, ok := a.t.(tHelper); ok {
|
||||
h.Helper()
|
||||
}
|
||||
InEpsilon(a.t, expected, actual, epsilon, msgAndArgs...)
|
||||
}
|
||||
|
||||
// InEpsilonSlice is the same as InEpsilon, except it compares each value from two slices.
|
||||
func (a *Assertions) InEpsilonSlice(expected interface{}, actual interface{}, epsilon float64, msgAndArgs ...interface{}) {
|
||||
if h, ok := a.t.(tHelper); ok {
|
||||
h.Helper()
|
||||
}
|
||||
InEpsilonSlice(a.t, expected, actual, epsilon, msgAndArgs...)
|
||||
}
|
||||
|
||||
// InEpsilonSlicef is the same as InEpsilon, except it compares each value from two slices.
|
||||
func (a *Assertions) InEpsilonSlicef(expected interface{}, actual interface{}, epsilon float64, msg string, args ...interface{}) {
|
||||
if h, ok := a.t.(tHelper); ok {
|
||||
h.Helper()
|
||||
}
|
||||
InEpsilonSlicef(a.t, expected, actual, epsilon, msg, args...)
|
||||
}
|
||||
|
||||
// InEpsilonf asserts that expected and actual have a relative error less than epsilon
|
||||
func (a *Assertions) InEpsilonf(expected interface{}, actual interface{}, epsilon float64, msg string, args ...interface{}) {
|
||||
if h, ok := a.t.(tHelper); ok {
|
||||
h.Helper()
|
||||
}
|
||||
InEpsilonf(a.t, expected, actual, epsilon, msg, args...)
|
||||
}
|
||||
|
||||
// IsType asserts that the specified objects are of the same type.
|
||||
func (a *Assertions) IsType(expectedType interface{}, object interface{}, msgAndArgs ...interface{}) {
|
||||
if h, ok := a.t.(tHelper); ok {
|
||||
h.Helper()
|
||||
}
|
||||
IsType(a.t, expectedType, object, msgAndArgs...)
|
||||
}
|
||||
|
||||
// IsTypef asserts that the specified objects are of the same type.
|
||||
func (a *Assertions) IsTypef(expectedType interface{}, object interface{}, msg string, args ...interface{}) {
|
||||
if h, ok := a.t.(tHelper); ok {
|
||||
h.Helper()
|
||||
}
|
||||
IsTypef(a.t, expectedType, object, msg, args...)
|
||||
}
|
||||
|
||||
// JSONEq asserts that two JSON strings are equivalent.
|
||||
//
|
||||
// a.JSONEq(`{"hello": "world", "foo": "bar"}`, `{"foo": "bar", "hello": "world"}`)
|
||||
func (a *Assertions) JSONEq(expected string, actual string, msgAndArgs ...interface{}) {
|
||||
if h, ok := a.t.(tHelper); ok {
|
||||
h.Helper()
|
||||
}
|
||||
JSONEq(a.t, expected, actual, msgAndArgs...)
|
||||
}
|
||||
|
||||
// JSONEqf asserts that two JSON strings are equivalent.
|
||||
//
|
||||
// a.JSONEqf(`{"hello": "world", "foo": "bar"}`, `{"foo": "bar", "hello": "world"}`, "error message %s", "formatted")
|
||||
func (a *Assertions) JSONEqf(expected string, actual string, msg string, args ...interface{}) {
|
||||
if h, ok := a.t.(tHelper); ok {
|
||||
h.Helper()
|
||||
}
|
||||
JSONEqf(a.t, expected, actual, msg, args...)
|
||||
}
|
||||
|
||||
// Len asserts that the specified object has specific length.
|
||||
// Len also fails if the object has a type that len() not accept.
|
||||
//
|
||||
// a.Len(mySlice, 3)
|
||||
func (a *Assertions) Len(object interface{}, length int, msgAndArgs ...interface{}) {
|
||||
if h, ok := a.t.(tHelper); ok {
|
||||
h.Helper()
|
||||
}
|
||||
Len(a.t, object, length, msgAndArgs...)
|
||||
}
|
||||
|
||||
// Lenf asserts that the specified object has specific length.
|
||||
// Lenf also fails if the object has a type that len() not accept.
|
||||
//
|
||||
// a.Lenf(mySlice, 3, "error message %s", "formatted")
|
||||
func (a *Assertions) Lenf(object interface{}, length int, msg string, args ...interface{}) {
|
||||
if h, ok := a.t.(tHelper); ok {
|
||||
h.Helper()
|
||||
}
|
||||
Lenf(a.t, object, length, msg, args...)
|
||||
}
|
||||
|
||||
// Nil asserts that the specified object is nil.
|
||||
//
|
||||
// a.Nil(err)
|
||||
func (a *Assertions) Nil(object interface{}, msgAndArgs ...interface{}) {
|
||||
if h, ok := a.t.(tHelper); ok {
|
||||
h.Helper()
|
||||
}
|
||||
Nil(a.t, object, msgAndArgs...)
|
||||
}
|
||||
|
||||
// Nilf asserts that the specified object is nil.
|
||||
//
|
||||
// a.Nilf(err, "error message %s", "formatted")
|
||||
func (a *Assertions) Nilf(object interface{}, msg string, args ...interface{}) {
|
||||
if h, ok := a.t.(tHelper); ok {
|
||||
h.Helper()
|
||||
}
|
||||
Nilf(a.t, object, msg, args...)
|
||||
}
|
||||
|
||||
// NoError asserts that a function returned no error (i.e. `nil`).
|
||||
//
|
||||
// actualObj, err := SomeFunction()
|
||||
// if a.NoError(err) {
|
||||
// assert.Equal(t, expectedObj, actualObj)
|
||||
// }
|
||||
func (a *Assertions) NoError(err error, msgAndArgs ...interface{}) {
|
||||
if h, ok := a.t.(tHelper); ok {
|
||||
h.Helper()
|
||||
}
|
||||
NoError(a.t, err, msgAndArgs...)
|
||||
}
|
||||
|
||||
// NoErrorf asserts that a function returned no error (i.e. `nil`).
|
||||
//
|
||||
// actualObj, err := SomeFunction()
|
||||
// if a.NoErrorf(err, "error message %s", "formatted") {
|
||||
// assert.Equal(t, expectedObj, actualObj)
|
||||
// }
|
||||
func (a *Assertions) NoErrorf(err error, msg string, args ...interface{}) {
|
||||
if h, ok := a.t.(tHelper); ok {
|
||||
h.Helper()
|
||||
}
|
||||
NoErrorf(a.t, err, msg, args...)
|
||||
}
|
||||
|
||||
// NotContains asserts that the specified string, list(array, slice...) or map does NOT contain the
|
||||
// specified substring or element.
|
||||
//
|
||||
// a.NotContains("Hello World", "Earth")
|
||||
// a.NotContains(["Hello", "World"], "Earth")
|
||||
// a.NotContains({"Hello": "World"}, "Earth")
|
||||
func (a *Assertions) NotContains(s interface{}, contains interface{}, msgAndArgs ...interface{}) {
|
||||
if h, ok := a.t.(tHelper); ok {
|
||||
h.Helper()
|
||||
}
|
||||
NotContains(a.t, s, contains, msgAndArgs...)
|
||||
}
|
||||
|
||||
// NotContainsf asserts that the specified string, list(array, slice...) or map does NOT contain the
|
||||
// specified substring or element.
|
||||
//
|
||||
// a.NotContainsf("Hello World", "Earth", "error message %s", "formatted")
|
||||
// a.NotContainsf(["Hello", "World"], "Earth", "error message %s", "formatted")
|
||||
// a.NotContainsf({"Hello": "World"}, "Earth", "error message %s", "formatted")
|
||||
func (a *Assertions) NotContainsf(s interface{}, contains interface{}, msg string, args ...interface{}) {
|
||||
if h, ok := a.t.(tHelper); ok {
|
||||
h.Helper()
|
||||
}
|
||||
NotContainsf(a.t, s, contains, msg, args...)
|
||||
}
|
||||
|
||||
// NotEmpty asserts that the specified object is NOT empty. I.e. not nil, "", false, 0 or either
|
||||
// a slice or a channel with len == 0.
|
||||
//
|
||||
// if a.NotEmpty(obj) {
|
||||
// assert.Equal(t, "two", obj[1])
|
||||
// }
|
||||
func (a *Assertions) NotEmpty(object interface{}, msgAndArgs ...interface{}) {
|
||||
if h, ok := a.t.(tHelper); ok {
|
||||
h.Helper()
|
||||
}
|
||||
NotEmpty(a.t, object, msgAndArgs...)
|
||||
}
|
||||
|
||||
// NotEmptyf asserts that the specified object is NOT empty. I.e. not nil, "", false, 0 or either
|
||||
// a slice or a channel with len == 0.
|
||||
//
|
||||
// if a.NotEmptyf(obj, "error message %s", "formatted") {
|
||||
// assert.Equal(t, "two", obj[1])
|
||||
// }
|
||||
func (a *Assertions) NotEmptyf(object interface{}, msg string, args ...interface{}) {
|
||||
if h, ok := a.t.(tHelper); ok {
|
||||
h.Helper()
|
||||
}
|
||||
NotEmptyf(a.t, object, msg, args...)
|
||||
}
|
||||
|
||||
// NotEqual asserts that the specified values are NOT equal.
|
||||
//
|
||||
// a.NotEqual(obj1, obj2)
|
||||
//
|
||||
// Pointer variable equality is determined based on the equality of the
|
||||
// referenced values (as opposed to the memory addresses).
|
||||
func (a *Assertions) NotEqual(expected interface{}, actual interface{}, msgAndArgs ...interface{}) {
|
||||
if h, ok := a.t.(tHelper); ok {
|
||||
h.Helper()
|
||||
}
|
||||
NotEqual(a.t, expected, actual, msgAndArgs...)
|
||||
}
|
||||
|
||||
// NotEqualf asserts that the specified values are NOT equal.
|
||||
//
|
||||
// a.NotEqualf(obj1, obj2, "error message %s", "formatted")
|
||||
//
|
||||
// Pointer variable equality is determined based on the equality of the
|
||||
// referenced values (as opposed to the memory addresses).
|
||||
func (a *Assertions) NotEqualf(expected interface{}, actual interface{}, msg string, args ...interface{}) {
|
||||
if h, ok := a.t.(tHelper); ok {
|
||||
h.Helper()
|
||||
}
|
||||
NotEqualf(a.t, expected, actual, msg, args...)
|
||||
}
|
||||
|
||||
// NotNil asserts that the specified object is not nil.
|
||||
//
|
||||
// a.NotNil(err)
|
||||
func (a *Assertions) NotNil(object interface{}, msgAndArgs ...interface{}) {
|
||||
if h, ok := a.t.(tHelper); ok {
|
||||
h.Helper()
|
||||
}
|
||||
NotNil(a.t, object, msgAndArgs...)
|
||||
}
|
||||
|
||||
// NotNilf asserts that the specified object is not nil.
|
||||
//
|
||||
// a.NotNilf(err, "error message %s", "formatted")
|
||||
func (a *Assertions) NotNilf(object interface{}, msg string, args ...interface{}) {
|
||||
if h, ok := a.t.(tHelper); ok {
|
||||
h.Helper()
|
||||
}
|
||||
NotNilf(a.t, object, msg, args...)
|
||||
}
|
||||
|
||||
// NotPanics asserts that the code inside the specified PanicTestFunc does NOT panic.
|
||||
//
|
||||
// a.NotPanics(func(){ RemainCalm() })
|
||||
func (a *Assertions) NotPanics(f assert.PanicTestFunc, msgAndArgs ...interface{}) {
|
||||
if h, ok := a.t.(tHelper); ok {
|
||||
h.Helper()
|
||||
}
|
||||
NotPanics(a.t, f, msgAndArgs...)
|
||||
}
|
||||
|
||||
// NotPanicsf asserts that the code inside the specified PanicTestFunc does NOT panic.
|
||||
//
|
||||
// a.NotPanicsf(func(){ RemainCalm() }, "error message %s", "formatted")
|
||||
func (a *Assertions) NotPanicsf(f assert.PanicTestFunc, msg string, args ...interface{}) {
|
||||
if h, ok := a.t.(tHelper); ok {
|
||||
h.Helper()
|
||||
}
|
||||
NotPanicsf(a.t, f, msg, args...)
|
||||
}
|
||||
|
||||
// NotRegexp asserts that a specified regexp does not match a string.
|
||||
//
|
||||
// a.NotRegexp(regexp.MustCompile("starts"), "it's starting")
|
||||
// a.NotRegexp("^start", "it's not starting")
|
||||
func (a *Assertions) NotRegexp(rx interface{}, str interface{}, msgAndArgs ...interface{}) {
|
||||
if h, ok := a.t.(tHelper); ok {
|
||||
h.Helper()
|
||||
}
|
||||
NotRegexp(a.t, rx, str, msgAndArgs...)
|
||||
}
|
||||
|
||||
// NotRegexpf asserts that a specified regexp does not match a string.
|
||||
//
|
||||
// a.NotRegexpf(regexp.MustCompile("starts", "error message %s", "formatted"), "it's starting")
|
||||
// a.NotRegexpf("^start", "it's not starting", "error message %s", "formatted")
|
||||
func (a *Assertions) NotRegexpf(rx interface{}, str interface{}, msg string, args ...interface{}) {
|
||||
if h, ok := a.t.(tHelper); ok {
|
||||
h.Helper()
|
||||
}
|
||||
NotRegexpf(a.t, rx, str, msg, args...)
|
||||
}
|
||||
|
||||
// NotSubset asserts that the specified list(array, slice...) contains not all
|
||||
// elements given in the specified subset(array, slice...).
|
||||
//
|
||||
// a.NotSubset([1, 3, 4], [1, 2], "But [1, 3, 4] does not contain [1, 2]")
|
||||
func (a *Assertions) NotSubset(list interface{}, subset interface{}, msgAndArgs ...interface{}) {
|
||||
if h, ok := a.t.(tHelper); ok {
|
||||
h.Helper()
|
||||
}
|
||||
NotSubset(a.t, list, subset, msgAndArgs...)
|
||||
}
|
||||
|
||||
// NotSubsetf asserts that the specified list(array, slice...) contains not all
|
||||
// elements given in the specified subset(array, slice...).
|
||||
//
|
||||
// a.NotSubsetf([1, 3, 4], [1, 2], "But [1, 3, 4] does not contain [1, 2]", "error message %s", "formatted")
|
||||
func (a *Assertions) NotSubsetf(list interface{}, subset interface{}, msg string, args ...interface{}) {
|
||||
if h, ok := a.t.(tHelper); ok {
|
||||
h.Helper()
|
||||
}
|
||||
NotSubsetf(a.t, list, subset, msg, args...)
|
||||
}
|
||||
|
||||
// NotZero asserts that i is not the zero value for its type.
|
||||
func (a *Assertions) NotZero(i interface{}, msgAndArgs ...interface{}) {
|
||||
if h, ok := a.t.(tHelper); ok {
|
||||
h.Helper()
|
||||
}
|
||||
NotZero(a.t, i, msgAndArgs...)
|
||||
}
|
||||
|
||||
// NotZerof asserts that i is not the zero value for its type.
|
||||
func (a *Assertions) NotZerof(i interface{}, msg string, args ...interface{}) {
|
||||
if h, ok := a.t.(tHelper); ok {
|
||||
h.Helper()
|
||||
}
|
||||
NotZerof(a.t, i, msg, args...)
|
||||
}
|
||||
|
||||
// Panics asserts that the code inside the specified PanicTestFunc panics.
|
||||
//
|
||||
// a.Panics(func(){ GoCrazy() })
|
||||
func (a *Assertions) Panics(f assert.PanicTestFunc, msgAndArgs ...interface{}) {
|
||||
if h, ok := a.t.(tHelper); ok {
|
||||
h.Helper()
|
||||
}
|
||||
Panics(a.t, f, msgAndArgs...)
|
||||
}
|
||||
|
||||
// PanicsWithValue asserts that the code inside the specified PanicTestFunc panics, and that
|
||||
// the recovered panic value equals the expected panic value.
|
||||
//
|
||||
// a.PanicsWithValue("crazy error", func(){ GoCrazy() })
|
||||
func (a *Assertions) PanicsWithValue(expected interface{}, f assert.PanicTestFunc, msgAndArgs ...interface{}) {
|
||||
if h, ok := a.t.(tHelper); ok {
|
||||
h.Helper()
|
||||
}
|
||||
PanicsWithValue(a.t, expected, f, msgAndArgs...)
|
||||
}
|
||||
|
||||
// PanicsWithValuef asserts that the code inside the specified PanicTestFunc panics, and that
|
||||
// the recovered panic value equals the expected panic value.
|
||||
//
|
||||
// a.PanicsWithValuef("crazy error", func(){ GoCrazy() }, "error message %s", "formatted")
|
||||
func (a *Assertions) PanicsWithValuef(expected interface{}, f assert.PanicTestFunc, msg string, args ...interface{}) {
|
||||
if h, ok := a.t.(tHelper); ok {
|
||||
h.Helper()
|
||||
}
|
||||
PanicsWithValuef(a.t, expected, f, msg, args...)
|
||||
}
|
||||
|
||||
// Panicsf asserts that the code inside the specified PanicTestFunc panics.
|
||||
//
|
||||
// a.Panicsf(func(){ GoCrazy() }, "error message %s", "formatted")
|
||||
func (a *Assertions) Panicsf(f assert.PanicTestFunc, msg string, args ...interface{}) {
|
||||
if h, ok := a.t.(tHelper); ok {
|
||||
h.Helper()
|
||||
}
|
||||
Panicsf(a.t, f, msg, args...)
|
||||
}
|
||||
|
||||
// Regexp asserts that a specified regexp matches a string.
|
||||
//
|
||||
// a.Regexp(regexp.MustCompile("start"), "it's starting")
|
||||
// a.Regexp("start...$", "it's not starting")
|
||||
func (a *Assertions) Regexp(rx interface{}, str interface{}, msgAndArgs ...interface{}) {
|
||||
if h, ok := a.t.(tHelper); ok {
|
||||
h.Helper()
|
||||
}
|
||||
Regexp(a.t, rx, str, msgAndArgs...)
|
||||
}
|
||||
|
||||
// Regexpf asserts that a specified regexp matches a string.
|
||||
//
|
||||
// a.Regexpf(regexp.MustCompile("start", "error message %s", "formatted"), "it's starting")
|
||||
// a.Regexpf("start...$", "it's not starting", "error message %s", "formatted")
|
||||
func (a *Assertions) Regexpf(rx interface{}, str interface{}, msg string, args ...interface{}) {
|
||||
if h, ok := a.t.(tHelper); ok {
|
||||
h.Helper()
|
||||
}
|
||||
Regexpf(a.t, rx, str, msg, args...)
|
||||
}
|
||||
|
||||
// Subset asserts that the specified list(array, slice...) contains all
|
||||
// elements given in the specified subset(array, slice...).
|
||||
//
|
||||
// a.Subset([1, 2, 3], [1, 2], "But [1, 2, 3] does contain [1, 2]")
|
||||
func (a *Assertions) Subset(list interface{}, subset interface{}, msgAndArgs ...interface{}) {
|
||||
if h, ok := a.t.(tHelper); ok {
|
||||
h.Helper()
|
||||
}
|
||||
Subset(a.t, list, subset, msgAndArgs...)
|
||||
}
|
||||
|
||||
// Subsetf asserts that the specified list(array, slice...) contains all
|
||||
// elements given in the specified subset(array, slice...).
|
||||
//
|
||||
// a.Subsetf([1, 2, 3], [1, 2], "But [1, 2, 3] does contain [1, 2]", "error message %s", "formatted")
|
||||
func (a *Assertions) Subsetf(list interface{}, subset interface{}, msg string, args ...interface{}) {
|
||||
if h, ok := a.t.(tHelper); ok {
|
||||
h.Helper()
|
||||
}
|
||||
Subsetf(a.t, list, subset, msg, args...)
|
||||
}
|
||||
|
||||
// True asserts that the specified value is true.
|
||||
//
|
||||
// a.True(myBool)
|
||||
func (a *Assertions) True(value bool, msgAndArgs ...interface{}) {
|
||||
if h, ok := a.t.(tHelper); ok {
|
||||
h.Helper()
|
||||
}
|
||||
True(a.t, value, msgAndArgs...)
|
||||
}
|
||||
|
||||
// Truef asserts that the specified value is true.
|
||||
//
|
||||
// a.Truef(myBool, "error message %s", "formatted")
|
||||
func (a *Assertions) Truef(value bool, msg string, args ...interface{}) {
|
||||
if h, ok := a.t.(tHelper); ok {
|
||||
h.Helper()
|
||||
}
|
||||
Truef(a.t, value, msg, args...)
|
||||
}
|
||||
|
||||
// WithinDuration asserts that the two times are within duration delta of each other.
|
||||
//
|
||||
// a.WithinDuration(time.Now(), time.Now(), 10*time.Second)
|
||||
func (a *Assertions) WithinDuration(expected time.Time, actual time.Time, delta time.Duration, msgAndArgs ...interface{}) {
|
||||
if h, ok := a.t.(tHelper); ok {
|
||||
h.Helper()
|
||||
}
|
||||
WithinDuration(a.t, expected, actual, delta, msgAndArgs...)
|
||||
}
|
||||
|
||||
// WithinDurationf asserts that the two times are within duration delta of each other.
|
||||
//
|
||||
// a.WithinDurationf(time.Now(), time.Now(), 10*time.Second, "error message %s", "formatted")
|
||||
func (a *Assertions) WithinDurationf(expected time.Time, actual time.Time, delta time.Duration, msg string, args ...interface{}) {
|
||||
if h, ok := a.t.(tHelper); ok {
|
||||
h.Helper()
|
||||
}
|
||||
WithinDurationf(a.t, expected, actual, delta, msg, args...)
|
||||
}
|
||||
|
||||
// Zero asserts that i is the zero value for its type.
|
||||
func (a *Assertions) Zero(i interface{}, msgAndArgs ...interface{}) {
|
||||
if h, ok := a.t.(tHelper); ok {
|
||||
h.Helper()
|
||||
}
|
||||
Zero(a.t, i, msgAndArgs...)
|
||||
}
|
||||
|
||||
// Zerof asserts that i is the zero value for its type.
|
||||
func (a *Assertions) Zerof(i interface{}, msg string, args ...interface{}) {
|
||||
if h, ok := a.t.(tHelper); ok {
|
||||
h.Helper()
|
||||
}
|
||||
Zerof(a.t, i, msg, args...)
|
||||
}
|
5
vendor/github.com/stretchr/testify/require/require_forward.go.tmpl
generated
vendored
Normal file
5
vendor/github.com/stretchr/testify/require/require_forward.go.tmpl
generated
vendored
Normal file
@ -0,0 +1,5 @@
|
||||
{{.CommentWithoutT "a"}}
|
||||
func (a *Assertions) {{.DocInfo.Name}}({{.Params}}) {
|
||||
if h, ok := a.t.(tHelper); ok { h.Helper() }
|
||||
{{.DocInfo.Name}}(a.t, {{.ForwardedParams}})
|
||||
}
|
29
vendor/github.com/stretchr/testify/require/requirements.go
generated
vendored
Normal file
29
vendor/github.com/stretchr/testify/require/requirements.go
generated
vendored
Normal file
@ -0,0 +1,29 @@
|
||||
package require
|
||||
|
||||
// TestingT is an interface wrapper around *testing.T
|
||||
type TestingT interface {
|
||||
Errorf(format string, args ...interface{})
|
||||
FailNow()
|
||||
}
|
||||
|
||||
type tHelper interface {
|
||||
Helper()
|
||||
}
|
||||
|
||||
// ComparisonAssertionFunc is a common function prototype when comparing two values. Can be useful
|
||||
// for table driven tests.
|
||||
type ComparisonAssertionFunc func(TestingT, interface{}, interface{}, ...interface{})
|
||||
|
||||
// ValueAssertionFunc is a common function prototype when validating a single value. Can be useful
|
||||
// for table driven tests.
|
||||
type ValueAssertionFunc func(TestingT, interface{}, ...interface{})
|
||||
|
||||
// BoolAssertionFunc is a common function prototype when validating a bool value. Can be useful
|
||||
// for table driven tests.
|
||||
type BoolAssertionFunc func(TestingT, bool, ...interface{})
|
||||
|
||||
// ErrorAssertionFunc is a common function prototype when validating an error value. Can be useful
|
||||
// for table driven tests.
|
||||
type ErrorAssertionFunc func(TestingT, error, ...interface{})
|
||||
|
||||
//go:generate go run ../_codegen/main.go -output-package=require -template=require.go.tmpl -include-format-funcs
|
124
vendor/golang.org/x/crypto/ripemd160/ripemd160.go
generated
vendored
Normal file
124
vendor/golang.org/x/crypto/ripemd160/ripemd160.go
generated
vendored
Normal file
@ -0,0 +1,124 @@
|
||||
// Copyright 2010 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
// Package ripemd160 implements the RIPEMD-160 hash algorithm.
|
||||
//
|
||||
// Deprecated: RIPEMD-160 is a legacy hash and should not be used for new
|
||||
// applications. Also, this package does not and will not provide an optimized
|
||||
// implementation. Instead, use a modern hash like SHA-256 (from crypto/sha256).
|
||||
package ripemd160 // import "golang.org/x/crypto/ripemd160"
|
||||
|
||||
// RIPEMD-160 is designed by Hans Dobbertin, Antoon Bosselaers, and Bart
|
||||
// Preneel with specifications available at:
|
||||
// http://homes.esat.kuleuven.be/~cosicart/pdf/AB-9601/AB-9601.pdf.
|
||||
|
||||
import (
|
||||
"crypto"
|
||||
"hash"
|
||||
)
|
||||
|
||||
func init() {
|
||||
crypto.RegisterHash(crypto.RIPEMD160, New)
|
||||
}
|
||||
|
||||
// The size of the checksum in bytes.
|
||||
const Size = 20
|
||||
|
||||
// The block size of the hash algorithm in bytes.
|
||||
const BlockSize = 64
|
||||
|
||||
const (
|
||||
_s0 = 0x67452301
|
||||
_s1 = 0xefcdab89
|
||||
_s2 = 0x98badcfe
|
||||
_s3 = 0x10325476
|
||||
_s4 = 0xc3d2e1f0
|
||||
)
|
||||
|
||||
// digest represents the partial evaluation of a checksum.
|
||||
type digest struct {
|
||||
s [5]uint32 // running context
|
||||
x [BlockSize]byte // temporary buffer
|
||||
nx int // index into x
|
||||
tc uint64 // total count of bytes processed
|
||||
}
|
||||
|
||||
func (d *digest) Reset() {
|
||||
d.s[0], d.s[1], d.s[2], d.s[3], d.s[4] = _s0, _s1, _s2, _s3, _s4
|
||||
d.nx = 0
|
||||
d.tc = 0
|
||||
}
|
||||
|
||||
// New returns a new hash.Hash computing the checksum.
|
||||
func New() hash.Hash {
|
||||
result := new(digest)
|
||||
result.Reset()
|
||||
return result
|
||||
}
|
||||
|
||||
func (d *digest) Size() int { return Size }
|
||||
|
||||
func (d *digest) BlockSize() int { return BlockSize }
|
||||
|
||||
func (d *digest) Write(p []byte) (nn int, err error) {
|
||||
nn = len(p)
|
||||
d.tc += uint64(nn)
|
||||
if d.nx > 0 {
|
||||
n := len(p)
|
||||
if n > BlockSize-d.nx {
|
||||
n = BlockSize - d.nx
|
||||
}
|
||||
for i := 0; i < n; i++ {
|
||||
d.x[d.nx+i] = p[i]
|
||||
}
|
||||
d.nx += n
|
||||
if d.nx == BlockSize {
|
||||
_Block(d, d.x[0:])
|
||||
d.nx = 0
|
||||
}
|
||||
p = p[n:]
|
||||
}
|
||||
n := _Block(d, p)
|
||||
p = p[n:]
|
||||
if len(p) > 0 {
|
||||
d.nx = copy(d.x[:], p)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (d0 *digest) Sum(in []byte) []byte {
|
||||
// Make a copy of d0 so that caller can keep writing and summing.
|
||||
d := *d0
|
||||
|
||||
// Padding. Add a 1 bit and 0 bits until 56 bytes mod 64.
|
||||
tc := d.tc
|
||||
var tmp [64]byte
|
||||
tmp[0] = 0x80
|
||||
if tc%64 < 56 {
|
||||
d.Write(tmp[0 : 56-tc%64])
|
||||
} else {
|
||||
d.Write(tmp[0 : 64+56-tc%64])
|
||||
}
|
||||
|
||||
// Length in bits.
|
||||
tc <<= 3
|
||||
for i := uint(0); i < 8; i++ {
|
||||
tmp[i] = byte(tc >> (8 * i))
|
||||
}
|
||||
d.Write(tmp[0:8])
|
||||
|
||||
if d.nx != 0 {
|
||||
panic("d.nx != 0")
|
||||
}
|
||||
|
||||
var digest [Size]byte
|
||||
for i, s := range d.s {
|
||||
digest[i*4] = byte(s)
|
||||
digest[i*4+1] = byte(s >> 8)
|
||||
digest[i*4+2] = byte(s >> 16)
|
||||
digest[i*4+3] = byte(s >> 24)
|
||||
}
|
||||
|
||||
return append(in, digest[:]...)
|
||||
}
|
165
vendor/golang.org/x/crypto/ripemd160/ripemd160block.go
generated
vendored
Normal file
165
vendor/golang.org/x/crypto/ripemd160/ripemd160block.go
generated
vendored
Normal file
@ -0,0 +1,165 @@
|
||||
// Copyright 2010 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
// RIPEMD-160 block step.
|
||||
// In its own file so that a faster assembly or C version
|
||||
// can be substituted easily.
|
||||
|
||||
package ripemd160
|
||||
|
||||
import (
|
||||
"math/bits"
|
||||
)
|
||||
|
||||
// work buffer indices and roll amounts for one line
|
||||
var _n = [80]uint{
|
||||
0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15,
|
||||
7, 4, 13, 1, 10, 6, 15, 3, 12, 0, 9, 5, 2, 14, 11, 8,
|
||||
3, 10, 14, 4, 9, 15, 8, 1, 2, 7, 0, 6, 13, 11, 5, 12,
|
||||
1, 9, 11, 10, 0, 8, 12, 4, 13, 3, 7, 15, 14, 5, 6, 2,
|
||||
4, 0, 5, 9, 7, 12, 2, 10, 14, 1, 3, 8, 11, 6, 15, 13,
|
||||
}
|
||||
|
||||
var _r = [80]uint{
|
||||
11, 14, 15, 12, 5, 8, 7, 9, 11, 13, 14, 15, 6, 7, 9, 8,
|
||||
7, 6, 8, 13, 11, 9, 7, 15, 7, 12, 15, 9, 11, 7, 13, 12,
|
||||
11, 13, 6, 7, 14, 9, 13, 15, 14, 8, 13, 6, 5, 12, 7, 5,
|
||||
11, 12, 14, 15, 14, 15, 9, 8, 9, 14, 5, 6, 8, 6, 5, 12,
|
||||
9, 15, 5, 11, 6, 8, 13, 12, 5, 12, 13, 14, 11, 8, 5, 6,
|
||||
}
|
||||
|
||||
// same for the other parallel one
|
||||
var n_ = [80]uint{
|
||||
5, 14, 7, 0, 9, 2, 11, 4, 13, 6, 15, 8, 1, 10, 3, 12,
|
||||
6, 11, 3, 7, 0, 13, 5, 10, 14, 15, 8, 12, 4, 9, 1, 2,
|
||||
15, 5, 1, 3, 7, 14, 6, 9, 11, 8, 12, 2, 10, 0, 4, 13,
|
||||
8, 6, 4, 1, 3, 11, 15, 0, 5, 12, 2, 13, 9, 7, 10, 14,
|
||||
12, 15, 10, 4, 1, 5, 8, 7, 6, 2, 13, 14, 0, 3, 9, 11,
|
||||
}
|
||||
|
||||
var r_ = [80]uint{
|
||||
8, 9, 9, 11, 13, 15, 15, 5, 7, 7, 8, 11, 14, 14, 12, 6,
|
||||
9, 13, 15, 7, 12, 8, 9, 11, 7, 7, 12, 7, 6, 15, 13, 11,
|
||||
9, 7, 15, 11, 8, 6, 6, 14, 12, 13, 5, 14, 13, 13, 7, 5,
|
||||
15, 5, 8, 11, 14, 14, 6, 14, 6, 9, 12, 9, 12, 5, 15, 8,
|
||||
8, 5, 12, 9, 12, 5, 14, 6, 8, 13, 6, 5, 15, 13, 11, 11,
|
||||
}
|
||||
|
||||
func _Block(md *digest, p []byte) int {
|
||||
n := 0
|
||||
var x [16]uint32
|
||||
var alpha, beta uint32
|
||||
for len(p) >= BlockSize {
|
||||
a, b, c, d, e := md.s[0], md.s[1], md.s[2], md.s[3], md.s[4]
|
||||
aa, bb, cc, dd, ee := a, b, c, d, e
|
||||
j := 0
|
||||
for i := 0; i < 16; i++ {
|
||||
x[i] = uint32(p[j]) | uint32(p[j+1])<<8 | uint32(p[j+2])<<16 | uint32(p[j+3])<<24
|
||||
j += 4
|
||||
}
|
||||
|
||||
// round 1
|
||||
i := 0
|
||||
for i < 16 {
|
||||
alpha = a + (b ^ c ^ d) + x[_n[i]]
|
||||
s := int(_r[i])
|
||||
alpha = bits.RotateLeft32(alpha, s) + e
|
||||
beta = bits.RotateLeft32(c, 10)
|
||||
a, b, c, d, e = e, alpha, b, beta, d
|
||||
|
||||
// parallel line
|
||||
alpha = aa + (bb ^ (cc | ^dd)) + x[n_[i]] + 0x50a28be6
|
||||
s = int(r_[i])
|
||||
alpha = bits.RotateLeft32(alpha, s) + ee
|
||||
beta = bits.RotateLeft32(cc, 10)
|
||||
aa, bb, cc, dd, ee = ee, alpha, bb, beta, dd
|
||||
|
||||
i++
|
||||
}
|
||||
|
||||
// round 2
|
||||
for i < 32 {
|
||||
alpha = a + (b&c | ^b&d) + x[_n[i]] + 0x5a827999
|
||||
s := int(_r[i])
|
||||
alpha = bits.RotateLeft32(alpha, s) + e
|
||||
beta = bits.RotateLeft32(c, 10)
|
||||
a, b, c, d, e = e, alpha, b, beta, d
|
||||
|
||||
// parallel line
|
||||
alpha = aa + (bb&dd | cc&^dd) + x[n_[i]] + 0x5c4dd124
|
||||
s = int(r_[i])
|
||||
alpha = bits.RotateLeft32(alpha, s) + ee
|
||||
beta = bits.RotateLeft32(cc, 10)
|
||||
aa, bb, cc, dd, ee = ee, alpha, bb, beta, dd
|
||||
|
||||
i++
|
||||
}
|
||||
|
||||
// round 3
|
||||
for i < 48 {
|
||||
alpha = a + (b | ^c ^ d) + x[_n[i]] + 0x6ed9eba1
|
||||
s := int(_r[i])
|
||||
alpha = bits.RotateLeft32(alpha, s) + e
|
||||
beta = bits.RotateLeft32(c, 10)
|
||||
a, b, c, d, e = e, alpha, b, beta, d
|
||||
|
||||
// parallel line
|
||||
alpha = aa + (bb | ^cc ^ dd) + x[n_[i]] + 0x6d703ef3
|
||||
s = int(r_[i])
|
||||
alpha = bits.RotateLeft32(alpha, s) + ee
|
||||
beta = bits.RotateLeft32(cc, 10)
|
||||
aa, bb, cc, dd, ee = ee, alpha, bb, beta, dd
|
||||
|
||||
i++
|
||||
}
|
||||
|
||||
// round 4
|
||||
for i < 64 {
|
||||
alpha = a + (b&d | c&^d) + x[_n[i]] + 0x8f1bbcdc
|
||||
s := int(_r[i])
|
||||
alpha = bits.RotateLeft32(alpha, s) + e
|
||||
beta = bits.RotateLeft32(c, 10)
|
||||
a, b, c, d, e = e, alpha, b, beta, d
|
||||
|
||||
// parallel line
|
||||
alpha = aa + (bb&cc | ^bb&dd) + x[n_[i]] + 0x7a6d76e9
|
||||
s = int(r_[i])
|
||||
alpha = bits.RotateLeft32(alpha, s) + ee
|
||||
beta = bits.RotateLeft32(cc, 10)
|
||||
aa, bb, cc, dd, ee = ee, alpha, bb, beta, dd
|
||||
|
||||
i++
|
||||
}
|
||||
|
||||
// round 5
|
||||
for i < 80 {
|
||||
alpha = a + (b ^ (c | ^d)) + x[_n[i]] + 0xa953fd4e
|
||||
s := int(_r[i])
|
||||
alpha = bits.RotateLeft32(alpha, s) + e
|
||||
beta = bits.RotateLeft32(c, 10)
|
||||
a, b, c, d, e = e, alpha, b, beta, d
|
||||
|
||||
// parallel line
|
||||
alpha = aa + (bb ^ cc ^ dd) + x[n_[i]]
|
||||
s = int(r_[i])
|
||||
alpha = bits.RotateLeft32(alpha, s) + ee
|
||||
beta = bits.RotateLeft32(cc, 10)
|
||||
aa, bb, cc, dd, ee = ee, alpha, bb, beta, dd
|
||||
|
||||
i++
|
||||
}
|
||||
|
||||
// combine results
|
||||
dd += c + md.s[1]
|
||||
md.s[1] = md.s[2] + d + ee
|
||||
md.s[2] = md.s[3] + e + aa
|
||||
md.s[3] = md.s[4] + a + bb
|
||||
md.s[4] = md.s[0] + b + cc
|
||||
md.s[0] = dd
|
||||
|
||||
p = p[BlockSize:]
|
||||
n += BlockSize
|
||||
}
|
||||
return n
|
||||
}
|
14
vendor/modules.txt
vendored
14
vendor/modules.txt
vendored
@ -50,6 +50,8 @@ github.com/aws/aws-sdk-go/private/protocol/jsonrpc
|
||||
github.com/aws/aws-sdk-go/private/protocol/xml/xmlutil
|
||||
github.com/aws/aws-sdk-go/private/protocol/query/queryutil
|
||||
github.com/aws/aws-sdk-go/private/protocol/json/jsonutil
|
||||
# github.com/beevik/etree v1.1.0
|
||||
github.com/beevik/etree
|
||||
# github.com/benbjohnson/clock v0.0.0-20161215174838-7dc76406b6d3
|
||||
github.com/benbjohnson/clock
|
||||
# github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973
|
||||
@ -58,6 +60,10 @@ github.com/beorn7/perks/quantile
|
||||
github.com/bradfitz/gomemcache/memcache
|
||||
# github.com/codegangsta/cli v1.20.0
|
||||
github.com/codegangsta/cli
|
||||
# github.com/crewjam/saml v0.0.0-20190521120225-344d075952c9
|
||||
github.com/crewjam/saml
|
||||
github.com/crewjam/saml/logger
|
||||
github.com/crewjam/saml/xmlenc
|
||||
# github.com/davecgh/go-spew v1.1.1
|
||||
github.com/davecgh/go-spew/spew
|
||||
# github.com/denisenkom/go-mssqldb v0.0.0-20190315220205-a8ed825ac853
|
||||
@ -124,6 +130,8 @@ github.com/hashicorp/yamux
|
||||
github.com/inconshreveable/log15
|
||||
# github.com/jmespath/go-jmespath v0.0.0-20180206201540-c2b33e8439af
|
||||
github.com/jmespath/go-jmespath
|
||||
# github.com/jonboulle/clockwork v0.1.0
|
||||
github.com/jonboulle/clockwork
|
||||
# github.com/jtolds/gls v4.20.0+incompatible
|
||||
github.com/jtolds/gls
|
||||
# github.com/klauspost/compress v1.4.1
|
||||
@ -177,6 +185,10 @@ github.com/prometheus/procfs/internal/util
|
||||
github.com/rainycape/unidecode
|
||||
# github.com/robfig/cron v0.0.0-20180505203441-b41be1df6967
|
||||
github.com/robfig/cron
|
||||
# github.com/russellhaering/goxmldsig v0.0.0-20180430223755-7acd5e4a6ef7
|
||||
github.com/russellhaering/goxmldsig
|
||||
github.com/russellhaering/goxmldsig/etreeutils
|
||||
github.com/russellhaering/goxmldsig/types
|
||||
# github.com/sergi/go-diff v1.0.0
|
||||
github.com/sergi/go-diff/diffmatchpatch
|
||||
# github.com/smartystreets/assertions v0.0.0-20190401211740-f487f9de1cd3
|
||||
@ -189,6 +201,7 @@ github.com/smartystreets/goconvey/convey
|
||||
github.com/smartystreets/goconvey/convey/reporting
|
||||
github.com/smartystreets/goconvey/convey/gotest
|
||||
# github.com/stretchr/testify v1.3.0
|
||||
github.com/stretchr/testify/require
|
||||
github.com/stretchr/testify/assert
|
||||
# github.com/teris-io/shortid v0.0.0-20171029131806-771a37caa5cf
|
||||
github.com/teris-io/shortid
|
||||
@ -224,6 +237,7 @@ github.com/yudai/golcs
|
||||
golang.org/x/crypto/pbkdf2
|
||||
golang.org/x/crypto/ed25519
|
||||
golang.org/x/crypto/md4
|
||||
golang.org/x/crypto/ripemd160
|
||||
golang.org/x/crypto/ed25519/internal/edwards25519
|
||||
# golang.org/x/net v0.0.0-20190415100556-4a65cf94b679
|
||||
golang.org/x/net/context/ctxhttp
|
||||
|
Loading…
Reference in New Issue
Block a user