Initial QSfera import
This commit is contained in:
@@ -0,0 +1 @@
|
||||
*.test
|
||||
+175
@@ -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.
|
||||
+103
@@ -0,0 +1,103 @@
|
||||
# goxmldsig
|
||||
|
||||

|
||||
[](https://godoc.org/github.com/russellhaering/goxmldsig)
|
||||
|
||||
XML Digital Signatures implemented in pure Go.
|
||||
|
||||
## Installation
|
||||
|
||||
Install `goxmldsig` using `go get`:
|
||||
|
||||
```
|
||||
$ go get github.com/russellhaering/goxmldsig
|
||||
```
|
||||
|
||||
## Usage
|
||||
|
||||
Include the [`types.Signature`](https://pkg.go.dev/github.com/russellhaering/goxmldsig/types#Signature) struct from this package in your application messages.
|
||||
|
||||
```go
|
||||
import (
|
||||
sigtypes "github.com/russellhaering/goxmldsig/types"
|
||||
)
|
||||
|
||||
type AppHdr struct {
|
||||
...
|
||||
Signature *sigtypes.Signature
|
||||
}
|
||||
```
|
||||
|
||||
### Signing
|
||||
|
||||
```go
|
||||
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.
|
||||
+5
@@ -0,0 +1,5 @@
|
||||
# Security Policy
|
||||
|
||||
## Reporting a Vulnerability
|
||||
|
||||
Security vulnerabilities can be reported using GitHub's [private vulnerability reporting tool](https://github.com/russellhaering/goxmldsig/security/advisories/new).
|
||||
+270
@@ -0,0 +1,270 @@
|
||||
package dsig
|
||||
|
||||
import (
|
||||
"maps"
|
||||
"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 NullCanonicalizer struct {
|
||||
}
|
||||
|
||||
func MakeNullCanonicalizer() Canonicalizer {
|
||||
return &NullCanonicalizer{}
|
||||
}
|
||||
|
||||
func (c *NullCanonicalizer) Algorithm() AlgorithmID {
|
||||
return AlgorithmID("NULL")
|
||||
}
|
||||
|
||||
func (c *NullCanonicalizer) Canonicalize(el *etree.Element) ([]byte, error) {
|
||||
return canonicalSerialize(canonicalPrep(el, false, true))
|
||||
}
|
||||
|
||||
type c14N10ExclusiveCanonicalizer struct {
|
||||
prefixList string
|
||||
comments bool
|
||||
}
|
||||
|
||||
// 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,
|
||||
comments: false,
|
||||
}
|
||||
}
|
||||
|
||||
// MakeC14N10ExclusiveWithCommentsCanonicalizerWithPrefixList constructs an exclusive Canonicalizer
|
||||
// from a PrefixList in NMTOKENS format (a white space separated list).
|
||||
func MakeC14N10ExclusiveWithCommentsCanonicalizerWithPrefixList(prefixList string) Canonicalizer {
|
||||
return &c14N10ExclusiveCanonicalizer{
|
||||
prefixList: prefixList,
|
||||
comments: true,
|
||||
}
|
||||
}
|
||||
|
||||
// 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, c.comments)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return canonicalSerialize(el)
|
||||
}
|
||||
|
||||
func (c *c14N10ExclusiveCanonicalizer) Algorithm() AlgorithmID {
|
||||
if c.comments {
|
||||
return CanonicalXML10ExclusiveWithCommentsAlgorithmId
|
||||
}
|
||||
return CanonicalXML10ExclusiveAlgorithmId
|
||||
}
|
||||
|
||||
type c14N11Canonicalizer struct {
|
||||
comments bool
|
||||
}
|
||||
|
||||
// MakeC14N11Canonicalizer constructs an inclusive canonicalizer.
|
||||
func MakeC14N11Canonicalizer() Canonicalizer {
|
||||
return &c14N11Canonicalizer{
|
||||
comments: false,
|
||||
}
|
||||
}
|
||||
|
||||
// MakeC14N11WithCommentsCanonicalizer constructs an inclusive canonicalizer.
|
||||
func MakeC14N11WithCommentsCanonicalizer() Canonicalizer {
|
||||
return &c14N11Canonicalizer{
|
||||
comments: true,
|
||||
}
|
||||
}
|
||||
|
||||
// Canonicalize transforms the input Element into a serialized XML document in canonical form.
|
||||
func (c *c14N11Canonicalizer) Canonicalize(el *etree.Element) ([]byte, error) {
|
||||
return canonicalSerialize(canonicalPrep(el, true, c.comments))
|
||||
}
|
||||
|
||||
func (c *c14N11Canonicalizer) Algorithm() AlgorithmID {
|
||||
if c.comments {
|
||||
return CanonicalXML11WithCommentsAlgorithmId
|
||||
}
|
||||
return CanonicalXML11AlgorithmId
|
||||
}
|
||||
|
||||
type c14N10RecCanonicalizer struct {
|
||||
comments bool
|
||||
}
|
||||
|
||||
// MakeC14N10RecCanonicalizer constructs an inclusive canonicalizer.
|
||||
func MakeC14N10RecCanonicalizer() Canonicalizer {
|
||||
return &c14N10RecCanonicalizer{
|
||||
comments: false,
|
||||
}
|
||||
}
|
||||
|
||||
// MakeC14N10WithCommentsCanonicalizer constructs an inclusive canonicalizer.
|
||||
func MakeC14N10WithCommentsCanonicalizer() Canonicalizer {
|
||||
return &c14N10RecCanonicalizer{
|
||||
comments: true,
|
||||
}
|
||||
}
|
||||
|
||||
// Canonicalize transforms the input Element into a serialized XML document in canonical form.
|
||||
func (c *c14N10RecCanonicalizer) Canonicalize(inputXML *etree.Element) ([]byte, error) {
|
||||
parentNamespaceAttributes, parentXmlAttributes := getParentNamespaceAndXmlAttributes(inputXML)
|
||||
inputXMLCopy := inputXML.Copy()
|
||||
enhanceNamespaceAttributes(inputXMLCopy, parentNamespaceAttributes, parentXmlAttributes)
|
||||
return canonicalSerialize(canonicalPrep(inputXMLCopy, true, c.comments))
|
||||
}
|
||||
|
||||
func (c *c14N10RecCanonicalizer) Algorithm() AlgorithmID {
|
||||
if c.comments {
|
||||
return CanonicalXML10WithCommentsAlgorithmId
|
||||
}
|
||||
return CanonicalXML10RecAlgorithmId
|
||||
|
||||
}
|
||||
|
||||
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, strip bool, comments bool) *etree.Element {
|
||||
return canonicalPrepInner(el, make(map[string]string), strip, comments)
|
||||
}
|
||||
|
||||
func canonicalPrepInner(el *etree.Element, seenSoFar map[string]string, strip bool, comments bool) *etree.Element {
|
||||
_seenSoFar := make(map[string]string)
|
||||
maps.Copy(_seenSoFar, seenSoFar)
|
||||
|
||||
ne := el.Copy()
|
||||
sort.Sort(etreeutils.SortedAttrs(ne.Attr))
|
||||
n := 0
|
||||
for _, attr := range ne.Attr {
|
||||
if attr.Space != nsSpace && !(attr.Space == "" && attr.Key == nsSpace) {
|
||||
ne.Attr[n] = attr
|
||||
n++
|
||||
continue
|
||||
}
|
||||
|
||||
if attr.Space == nsSpace {
|
||||
key := attr.Space + ":" + attr.Key
|
||||
if uri, seen := _seenSoFar[key]; !seen || attr.Value != uri {
|
||||
ne.Attr[n] = attr
|
||||
n++
|
||||
_seenSoFar[key] = attr.Value
|
||||
}
|
||||
} else {
|
||||
if uri, seen := _seenSoFar[nsSpace]; (!seen && attr.Value != "") || attr.Value != uri {
|
||||
ne.Attr[n] = attr
|
||||
n++
|
||||
_seenSoFar[nsSpace] = attr.Value
|
||||
}
|
||||
}
|
||||
}
|
||||
ne.Attr = ne.Attr[:n]
|
||||
|
||||
if !comments {
|
||||
c := 0
|
||||
for c < len(ne.Child) {
|
||||
if _, ok := ne.Child[c].(*etree.Comment); ok {
|
||||
ne.RemoveChildAt(c)
|
||||
} else {
|
||||
c++
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for i, token := range ne.Child {
|
||||
childElement, ok := token.(*etree.Element)
|
||||
if ok {
|
||||
ne.Child[i] = canonicalPrepInner(childElement, _seenSoFar, strip, comments)
|
||||
}
|
||||
}
|
||||
|
||||
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()
|
||||
}
|
||||
|
||||
func getParentNamespaceAndXmlAttributes(el *etree.Element) (map[string]string, map[string]string) {
|
||||
namespaceMap := make(map[string]string, 23)
|
||||
xmlMap := make(map[string]string, 5)
|
||||
parents := make([]*etree.Element, 0, 23)
|
||||
n1 := el.Parent()
|
||||
if n1 == nil {
|
||||
return namespaceMap, xmlMap
|
||||
}
|
||||
parent := n1
|
||||
for parent != nil {
|
||||
parents = append(parents, parent)
|
||||
parent = parent.Parent()
|
||||
}
|
||||
for i := len(parents) - 1; i > -1; i-- {
|
||||
elementPos := parents[i]
|
||||
for _, attr := range elementPos.Attr {
|
||||
if attr.Space == "xmlns" && (attr.Key != "xml" || attr.Value != "http://www.w3.org/XML/1998/namespace") {
|
||||
namespaceMap[attr.Key] = attr.Value
|
||||
} else if attr.Space == "" && attr.Key == "xmlns" {
|
||||
namespaceMap[attr.Key] = attr.Value
|
||||
} else if attr.Space == "xml" {
|
||||
xmlMap[attr.Key] = attr.Value
|
||||
}
|
||||
}
|
||||
}
|
||||
return namespaceMap, xmlMap
|
||||
}
|
||||
|
||||
func enhanceNamespaceAttributes(el *etree.Element, parentNamespaces map[string]string, parentXmlAttributes map[string]string) {
|
||||
for prefix, uri := range parentNamespaces {
|
||||
if prefix == "xmlns" {
|
||||
el.CreateAttr("xmlns", uri)
|
||||
} else {
|
||||
el.CreateAttr("xmlns:"+prefix, uri)
|
||||
}
|
||||
}
|
||||
for attr, value := range parentXmlAttributes {
|
||||
el.CreateAttr("xml:"+attr, value)
|
||||
}
|
||||
}
|
||||
+55
@@ -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),
|
||||
}
|
||||
}
|
||||
+110
@@ -0,0 +1,110 @@
|
||||
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, comments bool) error {
|
||||
prefixes := strings.Fields(inclusiveNamespacesPrefixList)
|
||||
prefixSet := make(map[string]struct{}, len(prefixes))
|
||||
|
||||
for _, prefix := range prefixes {
|
||||
prefixSet[prefix] = struct{}{}
|
||||
}
|
||||
|
||||
ctx := NewDefaultNSContext()
|
||||
err := transformExcC14n(ctx, ctx, el, prefixSet, comments)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func transformExcC14n(ctx, declared NSContext, el *etree.Element, inclusiveNamespaces map[string]struct{}, comments bool) error {
|
||||
scope, err := ctx.SubContext(el)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
visiblyUtilizedPrefixes := map[string]struct{}{
|
||||
el.Space: {},
|
||||
}
|
||||
|
||||
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))
|
||||
|
||||
if !comments {
|
||||
c := 0
|
||||
for c < len(el.Child) {
|
||||
if _, ok := el.Child[c].(*etree.Comment); ok {
|
||||
el.RemoveChildAt(c)
|
||||
} else {
|
||||
c++
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Transform child elements
|
||||
for _, child := range el.ChildElements() {
|
||||
err := transformExcC14n(scope, declared, child, inclusiveNamespaces, comments)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
+425
@@ -0,0 +1,425 @@
|
||||
package etreeutils
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"maps"
|
||||
"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/"
|
||||
)
|
||||
|
||||
func NewDefaultNSContext() NSContext {
|
||||
defaultLimit := 1000
|
||||
return NSContext{
|
||||
prefixes: map[string]string{
|
||||
defaultPrefix: XMLNamespace,
|
||||
xmlPrefix: XMLNamespace,
|
||||
xmlnsPrefix: XMLNSNamespace,
|
||||
},
|
||||
limit: &defaultLimit,
|
||||
}
|
||||
}
|
||||
|
||||
var (
|
||||
EmptyNSContext = NSContext{}
|
||||
|
||||
ErrReservedNamespace = errors.New("disallowed declaration of reserved namespace")
|
||||
ErrInvalidDefaultNamespace = errors.New("invalid default namespace declaration")
|
||||
ErrTraversalHalted = errors.New("traversal halted")
|
||||
ErrTraversalLimit = errors.New("traversal limit reached")
|
||||
)
|
||||
|
||||
type ErrUndeclaredNSPrefix struct {
|
||||
Prefix string
|
||||
}
|
||||
|
||||
func (e ErrUndeclaredNSPrefix) Error() string {
|
||||
return fmt.Sprintf("undeclared namespace prefix: '%s'", e.Prefix)
|
||||
}
|
||||
|
||||
type NSContext struct {
|
||||
prefixes map[string]string
|
||||
limit *int
|
||||
}
|
||||
|
||||
// CheckLimit checks the traversal limit before calling the handler function
|
||||
func (ctx NSContext) CheckLimit() error {
|
||||
if *ctx.limit <= 0 {
|
||||
return ErrTraversalLimit
|
||||
}
|
||||
*ctx.limit--
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (ctx NSContext) Copy() NSContext {
|
||||
prefixes := make(map[string]string, len(ctx.prefixes)+4)
|
||||
maps.Copy(prefixes, ctx.prefixes)
|
||||
|
||||
return NSContext{prefixes: prefixes, limit: ctx.limit}
|
||||
}
|
||||
|
||||
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))
|
||||
maps.Copy(prefixes, ctx.prefixes)
|
||||
|
||||
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 {
|
||||
err := ctx.CheckLimit()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
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(NewDefaultNSContext(), 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(NewDefaultNSContext(), 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(NewDefaultNSContext(), 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 := ctx.CheckLimit()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
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(NewDefaultNSContext(), 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 NewDefaultNSContext(), nil
|
||||
}
|
||||
|
||||
ctx, err := NSBuildParentContext(parent)
|
||||
if err != nil {
|
||||
return ctx, err
|
||||
}
|
||||
|
||||
return ctx.SubContext(parent)
|
||||
}
|
||||
+83
@@ -0,0 +1,83 @@
|
||||
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
|
||||
}
|
||||
|
||||
// Attributes with the same prefix should be sorted by their keys.
|
||||
if a[i].Space == a[j].Space {
|
||||
return a[i].Key < a[j].Key
|
||||
}
|
||||
|
||||
// Attributes in the same namespace are sorted by their Namespace URI, not the prefix.
|
||||
// NOTE: This implementation is not complete because it does not consider namespace
|
||||
// prefixes declared in ancestor elements. A complete solution would ideally use the
|
||||
// Attribute.NamespaceURI() method obtain a namespace URI for sorting, but the
|
||||
// beevik/etree library needs to be fixed to provide the correct value first.
|
||||
if a[i].Key == a[j].Key {
|
||||
var leftNS, rightNS etree.Attr
|
||||
for n := range a {
|
||||
if a[i].Space == a[n].Key {
|
||||
leftNS = a[n]
|
||||
}
|
||||
if a[j].Space == a[n].Key {
|
||||
rightNS = a[n]
|
||||
}
|
||||
}
|
||||
// Sort based on the NS URIs
|
||||
return leftNS.Value < rightNS.Value
|
||||
}
|
||||
|
||||
return a[i].Key < a[j].Key
|
||||
}
|
||||
+43
@@ -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 any) 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
@@ -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
@@ -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
|
||||
+334
@@ -0,0 +1,334 @@
|
||||
package dsig
|
||||
|
||||
import (
|
||||
"crypto"
|
||||
"crypto/ecdsa"
|
||||
"crypto/rand"
|
||||
"crypto/rsa"
|
||||
_ "crypto/sha1"
|
||||
_ "crypto/sha256"
|
||||
"crypto/x509"
|
||||
"encoding/base64"
|
||||
"errors"
|
||||
"fmt"
|
||||
|
||||
"github.com/beevik/etree"
|
||||
"github.com/russellhaering/goxmldsig/etreeutils"
|
||||
)
|
||||
|
||||
type SigningContext struct {
|
||||
Hash crypto.Hash
|
||||
|
||||
// This field will be nil and unused if the SigningContext is created with
|
||||
// NewSigningContext
|
||||
KeyStore X509KeyStore
|
||||
IdAttribute string
|
||||
Prefix string
|
||||
Canonicalizer Canonicalizer
|
||||
|
||||
// KeyStore is mutually exclusive with signer and certs
|
||||
signer crypto.Signer
|
||||
certs [][]byte
|
||||
}
|
||||
|
||||
func NewDefaultSigningContext(ks X509KeyStore) *SigningContext {
|
||||
return &SigningContext{
|
||||
Hash: crypto.SHA256,
|
||||
KeyStore: ks,
|
||||
IdAttribute: DefaultIdAttr,
|
||||
Prefix: DefaultPrefix,
|
||||
Canonicalizer: MakeC14N11Canonicalizer(),
|
||||
}
|
||||
}
|
||||
|
||||
// NewSigningContext creates a new signing context with the given signer and certificate chain.
|
||||
// Note that e.g. rsa.PrivateKey implements the crypto.Signer interface.
|
||||
// The certificate chain is a slice of ASN.1 DER-encoded X.509 certificates.
|
||||
// A SigningContext created with this function should not use the KeyStore field.
|
||||
// It will return error if passed a nil crypto.Signer
|
||||
func NewSigningContext(signer crypto.Signer, certs [][]byte) (*SigningContext, error) {
|
||||
if signer == nil {
|
||||
return nil, errors.New("signer cannot be nil for NewSigningContext")
|
||||
}
|
||||
ctx := &SigningContext{
|
||||
Hash: crypto.SHA256,
|
||||
IdAttribute: DefaultIdAttr,
|
||||
Prefix: DefaultPrefix,
|
||||
Canonicalizer: MakeC14N11Canonicalizer(),
|
||||
|
||||
signer: signer,
|
||||
certs: certs,
|
||||
}
|
||||
return ctx, nil
|
||||
}
|
||||
|
||||
func (ctx *SigningContext) getPublicKeyAlgorithm() x509.PublicKeyAlgorithm {
|
||||
if ctx.KeyStore != nil {
|
||||
return x509.RSA
|
||||
} else {
|
||||
switch ctx.signer.Public().(type) {
|
||||
case *ecdsa.PublicKey:
|
||||
return x509.ECDSA
|
||||
case *rsa.PublicKey:
|
||||
return x509.RSA
|
||||
}
|
||||
}
|
||||
|
||||
return x509.UnknownPublicKeyAlgorithm
|
||||
}
|
||||
|
||||
func (ctx *SigningContext) SetSignatureMethod(algorithmID string) error {
|
||||
info, ok := signatureMethodsByIdentifier[algorithmID]
|
||||
if !ok {
|
||||
return fmt.Errorf("unknown SignatureMethod: %s", algorithmID)
|
||||
}
|
||||
|
||||
algo := ctx.getPublicKeyAlgorithm()
|
||||
if info.PublicKeyAlgorithm != algo {
|
||||
return fmt.Errorf("SignatureMethod %s is incompatible with %s key", algorithmID, algo)
|
||||
}
|
||||
|
||||
ctx.Hash = info.Hash
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
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) signDigest(digest []byte) ([]byte, error) {
|
||||
if ctx.KeyStore != nil {
|
||||
key, _, err := ctx.KeyStore.GetKeyPair()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
rawSignature, err := rsa.SignPKCS1v15(rand.Reader, key, ctx.Hash, digest)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return rawSignature, nil
|
||||
} else {
|
||||
rawSignature, err := ctx.signer.Sign(rand.Reader, digest, ctx.Hash)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return rawSignature, nil
|
||||
}
|
||||
}
|
||||
|
||||
func (ctx *SigningContext) getCerts() ([][]byte, error) {
|
||||
if ctx.KeyStore != nil {
|
||||
if cs, ok := ctx.KeyStore.(X509ChainStore); ok {
|
||||
return cs.GetChain()
|
||||
}
|
||||
|
||||
_, cert, err := ctx.KeyStore.GetKeyPair()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return [][]byte{cert}, nil
|
||||
} else {
|
||||
return ctx.certs, nil
|
||||
}
|
||||
}
|
||||
|
||||
func (ctx *SigningContext) constructSignedInfo(el *etree.Element, enveloped bool) (*etree.Element, error) {
|
||||
digestAlgorithmIdentifier := ctx.GetDigestAlgorithmIdentifier()
|
||||
if digestAlgorithmIdentifier == "" {
|
||||
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 == "" {
|
||||
reference.CreateAttr(URIAttr, "")
|
||||
} else {
|
||||
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
|
||||
}
|
||||
|
||||
rawSignature, err := ctx.signDigest(digest)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
certs, err := ctx.getCerts()
|
||||
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 {
|
||||
algo := ctx.getPublicKeyAlgorithm()
|
||||
|
||||
if ident, ok := signatureMethodIdentifiers[algo][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)
|
||||
|
||||
return ctx.signDigest(digest)
|
||||
}
|
||||
+39
@@ -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
@@ -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
|
||||
}
|
||||
+584
@@ -0,0 +1,584 @@
|
||||
package dsig
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"crypto/x509"
|
||||
"encoding/base64"
|
||||
"encoding/xml"
|
||||
"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")
|
||||
ErrInvalidSignature = errors.New("Invalid Signature")
|
||||
)
|
||||
|
||||
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 childPath != 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
|
||||
|
||||
// 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 CanonicalXML10ExclusiveWithCommentsAlgorithmId:
|
||||
var prefixList string
|
||||
if transform.InclusiveNamespaces != nil {
|
||||
prefixList = transform.InclusiveNamespaces.PrefixList
|
||||
}
|
||||
|
||||
canonicalizer = MakeC14N10ExclusiveWithCommentsCanonicalizerWithPrefixList(prefixList)
|
||||
|
||||
case CanonicalXML11AlgorithmId:
|
||||
canonicalizer = MakeC14N11Canonicalizer()
|
||||
|
||||
case CanonicalXML11WithCommentsAlgorithmId:
|
||||
canonicalizer = MakeC14N11WithCommentsCanonicalizer()
|
||||
|
||||
case CanonicalXML10RecAlgorithmId:
|
||||
canonicalizer = MakeC14N10RecCanonicalizer()
|
||||
|
||||
case CanonicalXML10WithCommentsAlgorithmId:
|
||||
canonicalizer = MakeC14N10WithCommentsCanonicalizer()
|
||||
|
||||
default:
|
||||
return nil, nil, errors.New("Unknown Transform Algorithm: " + algo)
|
||||
}
|
||||
}
|
||||
|
||||
if canonicalizer == nil {
|
||||
canonicalizer = MakeNullCanonicalizer()
|
||||
}
|
||||
|
||||
return el, canonicalizer, nil
|
||||
}
|
||||
|
||||
// deprecated
|
||||
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) getCanonicalSignedInfo(sig *types.Signature) ([]byte, error) {
|
||||
signatureElement := sig.UnderlyingElement()
|
||||
|
||||
nsCtx, err := etreeutils.NSBuildParentContext(signatureElement)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
signedInfo, err := etreeutils.NSFindOneChildCtx(nsCtx, signatureElement, Namespace, SignedInfoTag)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if signedInfo == nil {
|
||||
return nil, errors.New("Missing SignedInfo")
|
||||
}
|
||||
|
||||
// Canonicalize the xml
|
||||
canonical, err := canonicalSerialize(signedInfo)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return canonical, nil
|
||||
}
|
||||
|
||||
// deprecated
|
||||
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
|
||||
}
|
||||
|
||||
algo, ok := x509SignatureAlgorithmByIdentifier[signatureMethodId]
|
||||
if !ok {
|
||||
return errors.New("Unknown signature method: " + signatureMethodId)
|
||||
}
|
||||
|
||||
err = cert.CheckSignature(algo, canonical, decodedSignature)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (ctx *ValidationContext) validateSignature(el *etree.Element, sig *types.Signature, cert *x509.Certificate) (*etree.Element, error) {
|
||||
|
||||
// Actually verify the 'SignedInfo' was signed by a trusted source
|
||||
signatureMethod := sig.SignedInfo.SignatureMethod.Algorithm
|
||||
|
||||
canonicalSignedInfoBytes, err := ctx.getCanonicalSignedInfo(sig)
|
||||
if err != nil {
|
||||
return nil, errors.New("Could not obtain canonical signed info bytes")
|
||||
}
|
||||
|
||||
if canonicalSignedInfoBytes == nil {
|
||||
return nil, errors.New("Missing SignedInfo")
|
||||
}
|
||||
|
||||
algo, ok := x509SignatureAlgorithmByIdentifier[signatureMethod]
|
||||
if !ok {
|
||||
return nil, errors.New("Unknown signature method: " + signatureMethod)
|
||||
}
|
||||
|
||||
if sig.SignatureValue == nil {
|
||||
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")
|
||||
}
|
||||
|
||||
err = cert.CheckSignature(algo, canonicalSignedInfoBytes, decodedSignature)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// only use the verified canonicalSignedInfoBytes
|
||||
// unmarshal canonicalSignedInfoBytes into a new SignedInfo type
|
||||
// to obtain the reference
|
||||
signedInfo := &types.SignedInfo{}
|
||||
err = xml.Unmarshal(canonicalSignedInfoBytes, signedInfo)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
idAttrEl := el.SelectAttr(ctx.IdAttribute)
|
||||
idAttr := ""
|
||||
if idAttrEl != nil {
|
||||
idAttr = idAttrEl.Value
|
||||
}
|
||||
|
||||
var ref *types.Reference
|
||||
|
||||
// Find the first reference which references the top-level element
|
||||
for i := range signedInfo.References {
|
||||
if signedInfo.References[i].URI == "" || signedInfo.References[i].URI[1:] == idAttr {
|
||||
ref = &signedInfo.References[i]
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
// prevents null pointer deref
|
||||
if ref == nil {
|
||||
return nil, errors.New("Missing reference")
|
||||
}
|
||||
|
||||
digestAlgorithmId := ref.DigestAlgo.Algorithm
|
||||
signedDigestValue, err := base64.StdEncoding.DecodeString(ref.DigestValue)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// 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
|
||||
}
|
||||
|
||||
referencedBytes, err := canonicalizer.Canonicalize(transformed)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// use a known digest hashing algorithm
|
||||
hashAlgorithm, ok := digestAlgorithmsByIdentifier[digestAlgorithmId]
|
||||
if !ok {
|
||||
return nil, errors.New("Unknown digest algorithm: " + digestAlgorithmId)
|
||||
}
|
||||
|
||||
hash := hashAlgorithm.New()
|
||||
_, err = hash.Write(referencedBytes)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
computedDigest := hash.Sum(nil)
|
||||
/* Digest the transformed XML and compare it to the 'DigestValue' from the 'SignedInfo'
|
||||
digest, err := ctx.digest(transformed, digestAlgorithm, canonicalizer)
|
||||
*/
|
||||
|
||||
if !bytes.Equal(computedDigest, signedDigestValue) {
|
||||
return nil, errors.New("Signature could not be verified")
|
||||
}
|
||||
|
||||
if !(len(computedDigest) >= 20) {
|
||||
return nil, errors.New("Computed digest is less than 20 something went wrong")
|
||||
}
|
||||
|
||||
// now only the referencedBytes is verified,
|
||||
// unmarshal into new etree
|
||||
doc := etree.NewDocument()
|
||||
err = doc.ReadFromBytes(referencedBytes)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return doc.Root(), nil
|
||||
}
|
||||
|
||||
func contains(roots []*x509.Certificate, cert *x509.Certificate) bool {
|
||||
for _, root := range roots {
|
||||
if root.Equal(cert) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// In most places, we use etree Elements, but while deserializing the Signature, we use
|
||||
// encoding/xml unmarshal directly to convert to a convenient go struct. This presents a problem in some cases because
|
||||
// when an xml element repeats under the parent, the last element will win and/or be appended. We need to assert that
|
||||
// the Signature object matches the expected shape of a Signature object.
|
||||
func validateShape(signatureEl *etree.Element) error {
|
||||
children := signatureEl.ChildElements()
|
||||
|
||||
childCounts := map[string]int{}
|
||||
for _, child := range children {
|
||||
childCounts[child.Tag]++
|
||||
}
|
||||
|
||||
validateCount := childCounts[SignedInfoTag] == 1 && childCounts[KeyInfoTag] <= 1 && childCounts[SignatureValueTag] == 1
|
||||
if !validateCount {
|
||||
return ErrInvalidSignature
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// findSignature searches for a Signature element referencing the passed root element.
|
||||
func (ctx *ValidationContext) findSignature(root *etree.Element) (*types.Signature, error) {
|
||||
idAttrEl := root.SelectAttr(ctx.IdAttribute)
|
||||
idAttr := ""
|
||||
if idAttrEl != nil {
|
||||
idAttr = idAttrEl.Value
|
||||
}
|
||||
|
||||
var sig *types.Signature
|
||||
|
||||
// Traverse the tree looking for a Signature element
|
||||
err := etreeutils.NSFindIterate(root, Namespace, SignatureTag, func(ctx etreeutils.NSContext, signatureEl *etree.Element) error {
|
||||
err := validateShape(signatureEl)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
found := false
|
||||
err = etreeutils.NSFindChildrenIterateCtx(ctx, signatureEl, 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 alg := AlgorithmID(c14NAlgorithm); alg {
|
||||
case CanonicalXML10ExclusiveAlgorithmId, CanonicalXML10ExclusiveWithCommentsAlgorithmId:
|
||||
err := etreeutils.TransformExcC14n(detachedSignedInfo, "", alg == CanonicalXML10ExclusiveWithCommentsAlgorithmId)
|
||||
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, CanonicalXML10RecAlgorithmId:
|
||||
canonicalSignedInfo = canonicalPrep(detachedSignedInfo, true, false)
|
||||
|
||||
case CanonicalXML11WithCommentsAlgorithmId, CanonicalXML10WithCommentsAlgorithmId:
|
||||
canonicalSignedInfo = canonicalPrep(detachedSignedInfo, true, true)
|
||||
|
||||
default:
|
||||
return fmt.Errorf("invalid CanonicalizationMethod on Signature: %s", c14NAlgorithm)
|
||||
}
|
||||
|
||||
signatureEl.InsertChildAt(signedInfo.Index(), canonicalSignedInfo)
|
||||
signatureEl.RemoveChild(signedInfo)
|
||||
|
||||
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, signatureEl, _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 {
|
||||
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 untrustedCert *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")
|
||||
}
|
||||
|
||||
untrustedCert, 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 {
|
||||
untrustedCert = roots[0]
|
||||
} else {
|
||||
return nil, errors.New("Missing x509 Element")
|
||||
}
|
||||
}
|
||||
|
||||
rootIdx := -1
|
||||
for i, root := range roots {
|
||||
if root.Equal(untrustedCert) {
|
||||
rootIdx = i
|
||||
}
|
||||
}
|
||||
|
||||
if rootIdx == -1 {
|
||||
return nil, errors.New("Could not verify certificate against trusted certs")
|
||||
}
|
||||
var trustedCert *x509.Certificate
|
||||
|
||||
trustedCert = roots[rootIdx]
|
||||
|
||||
// Verify that the certificate is one we trust
|
||||
|
||||
if now.Before(trustedCert.NotBefore) || now.After(trustedCert.NotAfter) {
|
||||
return nil, errors.New("Cert is not valid at this time")
|
||||
}
|
||||
|
||||
return trustedCert, 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
|
||||
}
|
||||
|
||||
// function to get the trusted certificate
|
||||
cert, err := ctx.verifyCertificate(sig)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return ctx.validateSignature(el, sig, cert)
|
||||
}
|
||||
+123
@@ -0,0 +1,123 @@
|
||||
package dsig
|
||||
|
||||
import (
|
||||
"crypto"
|
||||
"crypto/x509"
|
||||
)
|
||||
|
||||
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"
|
||||
RSASHA384SignatureMethod = "http://www.w3.org/2001/04/xmldsig-more#rsa-sha384"
|
||||
RSASHA512SignatureMethod = "http://www.w3.org/2001/04/xmldsig-more#rsa-sha512"
|
||||
ECDSASHA1SignatureMethod = "http://www.w3.org/2001/04/xmldsig-more#ecdsa-sha1"
|
||||
ECDSASHA256SignatureMethod = "http://www.w3.org/2001/04/xmldsig-more#ecdsa-sha256"
|
||||
ECDSASHA384SignatureMethod = "http://www.w3.org/2001/04/xmldsig-more#ecdsa-sha384"
|
||||
ECDSASHA512SignatureMethod = "http://www.w3.org/2001/04/xmldsig-more#ecdsa-sha512"
|
||||
)
|
||||
|
||||
// Well-known signature algorithms
|
||||
const (
|
||||
// Supported canonicalization algorithms
|
||||
CanonicalXML10ExclusiveAlgorithmId AlgorithmID = "http://www.w3.org/2001/10/xml-exc-c14n#"
|
||||
CanonicalXML10ExclusiveWithCommentsAlgorithmId AlgorithmID = "http://www.w3.org/2001/10/xml-exc-c14n#WithComments"
|
||||
|
||||
CanonicalXML11AlgorithmId AlgorithmID = "http://www.w3.org/2006/12/xml-c14n11"
|
||||
CanonicalXML11WithCommentsAlgorithmId AlgorithmID = "http://www.w3.org/2006/12/xml-c14n11#WithComments"
|
||||
|
||||
CanonicalXML10RecAlgorithmId AlgorithmID = "http://www.w3.org/TR/2001/REC-xml-c14n-20010315"
|
||||
CanonicalXML10WithCommentsAlgorithmId 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.SHA384: "http://www.w3.org/2001/04/xmldsig-more#sha384",
|
||||
crypto.SHA512: "http://www.w3.org/2001/04/xmlenc#sha512",
|
||||
}
|
||||
|
||||
type signatureMethodInfo struct {
|
||||
PublicKeyAlgorithm x509.PublicKeyAlgorithm
|
||||
Hash crypto.Hash
|
||||
}
|
||||
|
||||
var digestAlgorithmsByIdentifier = map[string]crypto.Hash{}
|
||||
var signatureMethodsByIdentifier = map[string]signatureMethodInfo{}
|
||||
|
||||
func init() {
|
||||
for hash, id := range digestAlgorithmIdentifiers {
|
||||
digestAlgorithmsByIdentifier[id] = hash
|
||||
}
|
||||
for algo, hashToMethod := range signatureMethodIdentifiers {
|
||||
for hash, method := range hashToMethod {
|
||||
signatureMethodsByIdentifier[method] = signatureMethodInfo{
|
||||
PublicKeyAlgorithm: algo,
|
||||
Hash: hash,
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var signatureMethodIdentifiers = map[x509.PublicKeyAlgorithm]map[crypto.Hash]string{
|
||||
x509.RSA: {
|
||||
crypto.SHA1: RSASHA1SignatureMethod,
|
||||
crypto.SHA256: RSASHA256SignatureMethod,
|
||||
crypto.SHA384: RSASHA384SignatureMethod,
|
||||
crypto.SHA512: RSASHA512SignatureMethod,
|
||||
},
|
||||
x509.ECDSA: {
|
||||
crypto.SHA1: ECDSASHA1SignatureMethod,
|
||||
crypto.SHA256: ECDSASHA256SignatureMethod,
|
||||
crypto.SHA384: ECDSASHA384SignatureMethod,
|
||||
crypto.SHA512: ECDSASHA512SignatureMethod,
|
||||
},
|
||||
}
|
||||
|
||||
var x509SignatureAlgorithmByIdentifier = map[string]x509.SignatureAlgorithm{
|
||||
RSASHA1SignatureMethod: x509.SHA1WithRSA,
|
||||
RSASHA256SignatureMethod: x509.SHA256WithRSA,
|
||||
RSASHA384SignatureMethod: x509.SHA384WithRSA,
|
||||
RSASHA512SignatureMethod: x509.SHA512WithRSA,
|
||||
ECDSASHA1SignatureMethod: x509.ECDSAWithSHA1,
|
||||
ECDSASHA256SignatureMethod: x509.ECDSAWithSHA256,
|
||||
ECDSASHA384SignatureMethod: x509.ECDSAWithSHA384,
|
||||
ECDSASHA512SignatureMethod: x509.ECDSAWithSHA512,
|
||||
}
|
||||
Reference in New Issue
Block a user