pax_global_header00006660000000000000000000000064141123266420014513gustar00rootroot0000000000000052 comment=dfbd95396ace7af04cffbd0e233a16a6afa5db59 goxmldsig-1.1.1/000077500000000000000000000000001411232664200135105ustar00rootroot00000000000000goxmldsig-1.1.1/.gitignore000066400000000000000000000000071411232664200154750ustar00rootroot00000000000000*.test goxmldsig-1.1.1/.travis.yml000066400000000000000000000001441411232664200156200ustar00rootroot00000000000000arch: - amd64 - ppc64le language: go go: - "1.14.x" - "1.15.x" - "1.17.x" - master goxmldsig-1.1.1/LICENSE000066400000000000000000000236361411232664200145270ustar00rootroot00000000000000 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. goxmldsig-1.1.1/README.md000066400000000000000000000046171411232664200147770ustar00rootroot00000000000000# goxmldsig [![Build Status](https://travis-ci.org/russellhaering/goxmldsig.svg?branch=master)](https://travis-ci.org/russellhaering/goxmldsig) [![GoDoc](https://godoc.org/github.com/russellhaering/goxmldsig?status.svg)](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 ### 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. goxmldsig-1.1.1/canonicalize.go000066400000000000000000000113251411232664200165000ustar00rootroot00000000000000package 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 NullCanonicalizer struct { } func MakeNullCanonicalizer() Canonicalizer { return &NullCanonicalizer{} } func (c *NullCanonicalizer) Algorithm() AlgorithmID { return AlgorithmID("NULL") } func (c *NullCanonicalizer) Canonicalize(el *etree.Element) ([]byte, error) { scope := make(map[string]struct{}) return canonicalSerialize(canonicalPrep(el, scope, false)) } 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, true)) } 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, true)) } 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, true)) } 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{}, strip bool) *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, strip) } } 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() } goxmldsig-1.1.1/canonicalize_test.go000066400000000000000000000120571411232664200175420ustar00rootroot00000000000000package dsig import ( "testing" "github.com/beevik/etree" "github.com/stretchr/testify/require" ) const ( assertion = `https://saml2.test.astuart.co/sso/saml2urn:oasis:names:tc:SAML:2.0:ac:classes:PasswordProtectedTransport` c14n11 = `https://saml2.test.astuart.co/sso/saml2urn:oasis:names:tc:SAML:2.0:ac:classes:PasswordProtectedTransport` assertionC14ned = `https://saml2.test.astuart.co/sso/saml2urn:oasis:names:tc:SAML:2.0:ac:classes:PasswordProtectedTransport` ) const ( xmldoc = `` xmldocC14N10ExclusiveCanonicalized = `` xmldocC14N11Canonicalized = `` ) func runCanonicalizationTest(t *testing.T, canonicalizer Canonicalizer, xmlstr string, canonicalXmlstr string) { raw := etree.NewDocument() err := raw.ReadFromString(xmlstr) require.NoError(t, err) canonicalized, err := canonicalizer.Canonicalize(raw.Root()) require.NoError(t, err) require.Equal(t, canonicalXmlstr, string(canonicalized)) } func TestExcC14N10(t *testing.T) { runCanonicalizationTest(t, MakeC14N10ExclusiveCanonicalizerWithPrefixList(""), assertion, assertionC14ned) } func TestC14N11(t *testing.T) { runCanonicalizationTest(t, MakeC14N11Canonicalizer(), assertion, c14n11) } func TestXmldocC14N10Exclusive(t *testing.T) { runCanonicalizationTest(t, MakeC14N10ExclusiveCanonicalizerWithPrefixList(""), xmldoc, xmldocC14N10ExclusiveCanonicalized) } func TestXmldocC14N11(t *testing.T) { runCanonicalizationTest(t, MakeC14N11Canonicalizer(), xmldoc, xmldocC14N11Canonicalized) } func TestExcC14nDefaultNamespace(t *testing.T) { input := `` expected := `` runCanonicalizationTest(t, MakeC14N10ExclusiveCanonicalizerWithPrefixList(""), input, expected) } func TestExcC14nWithPrefixList(t *testing.T) { input := `` expected := `` canonicalizer := MakeC14N10ExclusiveCanonicalizerWithPrefixList("xs") runCanonicalizationTest(t, canonicalizer, input, expected) } func TestExcC14nRedeclareDefaultNamespace(t *testing.T) { input := `` expected := `` canonicalizer := MakeC14N10ExclusiveCanonicalizerWithPrefixList("") runCanonicalizationTest(t, canonicalizer, input, expected) } goxmldsig-1.1.1/clock.go000066400000000000000000000022031411232664200151270ustar00rootroot00000000000000package 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), } } goxmldsig-1.1.1/etreeutils/000077500000000000000000000000001411232664200156755ustar00rootroot00000000000000goxmldsig-1.1.1/etreeutils/canonicalize.go000066400000000000000000000046661411232664200206770ustar00rootroot00000000000000package 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 } goxmldsig-1.1.1/etreeutils/namespace.go000066400000000000000000000251301411232664200201610ustar00rootroot00000000000000package 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:" // 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) } goxmldsig-1.1.1/etreeutils/sort.go000066400000000000000000000031241411232664200172130ustar00rootroot00000000000000package 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 } goxmldsig-1.1.1/etreeutils/unmarshal.go000066400000000000000000000017311411232664200202200ustar00rootroot00000000000000package 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 } goxmldsig-1.1.1/go.mod000066400000000000000000000006141411232664200146170ustar00rootroot00000000000000module github.com/russellhaering/goxmldsig go 1.15 require ( github.com/beevik/etree v1.1.0 github.com/jonboulle/clockwork v0.2.2 github.com/kr/pretty v0.3.0 // indirect github.com/rogpeppe/go-internal v1.8.0 // indirect github.com/stretchr/testify v1.6.1 gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c // indirect gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b // indirect ) goxmldsig-1.1.1/go.sum000066400000000000000000000057641411232664200146570ustar00rootroot00000000000000github.com/beevik/etree v1.1.0 h1:T0xke/WvNtMoCqgzPhkX2r4rjY3GDZFi+FjpRZY2Jbs= github.com/beevik/etree v1.1.0/go.mod h1:r8Aw8JqVegEf0w2fDnATrX9VpkMcyFeM0FhwO62wh+A= github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/jonboulle/clockwork v0.2.0 h1:J2SLSdy7HgElq8ekSl2Mxh6vrRNFxqbXGenYH2I02Vs= github.com/jonboulle/clockwork v0.2.0/go.mod h1:Pkfl5aHPm1nk2H9h0bjmnJD/BcgbGXUBGnn1kMkgxc8= github.com/jonboulle/clockwork v0.2.2 h1:UOGuzwb1PwsrDAObMuhUnj0p5ULPj8V/xJ7Kx9qUBdQ= github.com/jonboulle/clockwork v0.2.2/go.mod h1:Pkfl5aHPm1nk2H9h0bjmnJD/BcgbGXUBGnn1kMkgxc8= github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= github.com/kr/pretty v0.3.0 h1:WgNl7dwNpEZ6jJ9k1snq4pZsg7DOEN8hP9Xw0Tsjwk0= github.com/kr/pretty v0.3.0/go.mod h1:640gp4NfQd8pI5XOwp5fnNeVWj67G7CFk/SaSQn7NBk= github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/rogpeppe/go-internal v1.6.1/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc= github.com/rogpeppe/go-internal v1.8.0 h1:FCbCCtXNOY3UtUuHUYaghJg4y7Fd14rXifAYUAtL9R8= github.com/rogpeppe/go-internal v1.8.0/go.mod h1:WmiCO8CzOY8rg0OYDC4/i/2WRWAB6poM+XZ2dLUbcbE= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/testify v1.6.1 h1:hDPOHmpOpP40lSULcqw7IrRb/u7w6RpDC9399XyoNd0= github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b h1:h8qDotaEPuJATrMmW04NCwg7v22aHH28wwpauUhK9Oo= gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= goxmldsig-1.1.1/keystore.go000066400000000000000000000025531411232664200157110ustar00rootroot00000000000000package 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, } } goxmldsig-1.1.1/run_test.sh000077500000000000000000000003631411232664200157140ustar00rootroot00000000000000#!/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 goxmldsig-1.1.1/sign.go000066400000000000000000000160141411232664200150010ustar00rootroot00000000000000package 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 == "" { 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 } 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 } goxmldsig-1.1.1/sign_test.go000066400000000000000000000075021411232664200160420ustar00rootroot00000000000000package dsig import ( "crypto" "encoding/base64" "testing" "github.com/beevik/etree" "github.com/stretchr/testify/require" ) func TestSign(t *testing.T) { randomKeyStore := RandomKeyStoreForTest() ctx := NewDefaultSigningContext(randomKeyStore) authnRequest := &etree.Element{ Space: "samlp", Tag: "AuthnRequest", } id := "_97e34c50-65ec-4132-8b39-02933960a96a" authnRequest.CreateAttr("ID", id) hash := crypto.SHA256.New() canonicalized, err := ctx.Canonicalizer.Canonicalize(authnRequest) require.NoError(t, err) _, err = hash.Write(canonicalized) require.NoError(t, err) digest := hash.Sum(nil) signed, err := ctx.SignEnveloped(authnRequest) require.NoError(t, err) require.NotEmpty(t, signed) sig := signed.FindElement("//" + SignatureTag) require.NotEmpty(t, sig) signedInfo := sig.FindElement("//" + SignedInfoTag) require.NotEmpty(t, signedInfo) canonicalizationMethodElement := signedInfo.FindElement("//" + CanonicalizationMethodTag) require.NotEmpty(t, canonicalizationMethodElement) canonicalizationMethodAttr := canonicalizationMethodElement.SelectAttr(AlgorithmAttr) require.NotEmpty(t, canonicalizationMethodAttr) require.Equal(t, CanonicalXML11AlgorithmId.String(), canonicalizationMethodAttr.Value) signatureMethodElement := signedInfo.FindElement("//" + SignatureMethodTag) require.NotEmpty(t, signatureMethodElement) signatureMethodAttr := signatureMethodElement.SelectAttr(AlgorithmAttr) require.NotEmpty(t, signatureMethodAttr) require.Equal(t, "http://www.w3.org/2001/04/xmldsig-more#rsa-sha256", signatureMethodAttr.Value) referenceElement := signedInfo.FindElement("//" + ReferenceTag) require.NotEmpty(t, referenceElement) idAttr := referenceElement.SelectAttr(URIAttr) require.NotEmpty(t, idAttr) require.Equal(t, "#"+id, idAttr.Value) transformsElement := referenceElement.FindElement("//" + TransformsTag) require.NotEmpty(t, transformsElement) transformElement := transformsElement.FindElement("//" + TransformTag) require.NotEmpty(t, transformElement) algorithmAttr := transformElement.SelectAttr(AlgorithmAttr) require.NotEmpty(t, algorithmAttr) require.Equal(t, EnvelopedSignatureAltorithmId.String(), algorithmAttr.Value) digestMethodElement := referenceElement.FindElement("//" + DigestMethodTag) require.NotEmpty(t, digestMethodElement) digestMethodAttr := digestMethodElement.SelectAttr(AlgorithmAttr) require.NotEmpty(t, digestMethodElement) require.Equal(t, "http://www.w3.org/2001/04/xmlenc#sha256", digestMethodAttr.Value) digestValueElement := referenceElement.FindElement("//" + DigestValueTag) require.NotEmpty(t, digestValueElement) require.Equal(t, base64.StdEncoding.EncodeToString(digest), digestValueElement.Text()) } func TestSignErrors(t *testing.T) { randomKeyStore := RandomKeyStoreForTest() ctx := &SigningContext{ Hash: crypto.SHA512_256, KeyStore: randomKeyStore, IdAttribute: DefaultIdAttr, Prefix: DefaultPrefix, } authnRequest := &etree.Element{ Space: "samlp", Tag: "AuthnRequest", } _, err := ctx.SignEnveloped(authnRequest) require.Error(t, err) } func TestSignNonDefaultID(t *testing.T) { // Sign a document by referencing a non-default ID attribute ("OtherID"), // and confirm that the signature correctly references it. ks := RandomKeyStoreForTest() ctx := &SigningContext{ Hash: crypto.SHA256, KeyStore: ks, IdAttribute: "OtherID", Prefix: DefaultPrefix, Canonicalizer: MakeC14N11Canonicalizer(), } signable := &etree.Element{ Space: "foo", Tag: "Bar", } id := "_97e34c50-65ec-4132-8b39-02933960a96b" signable.CreateAttr("OtherID", id) signed, err := ctx.SignEnveloped(signable) require.NoError(t, err) ref := signed.FindElement("./Signature/SignedInfo/Reference") require.NotNil(t, ref) refURI := ref.SelectAttrValue("URI", "") require.Equal(t, refURI, "#"+id) } goxmldsig-1.1.1/tls_keystore.go000066400000000000000000000015541411232664200165730ustar00rootroot00000000000000package 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 } goxmldsig-1.1.1/types/000077500000000000000000000000001411232664200146545ustar00rootroot00000000000000goxmldsig-1.1.1/types/signature.go000066400000000000000000000057071411232664200172150ustar00rootroot00000000000000package 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 } goxmldsig-1.1.1/validate.go000066400000000000000000000321241411232664200156320ustar00rootroot00000000000000package 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") 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 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 // 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 { canonicalizer = MakeNullCanonicalizer() } 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) { 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 _, _ref := range sig.SignedInfo.References { if _ref.URI == "" || _ref.URI[1:] == idAttr { 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") } 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") } // 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 } // 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 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{}{}, true) case CanonicalXML10RecAlgorithmId: canonicalSignedInfo = canonicalPrep(detachedSignedInfo, map[string]struct{}{}, true) case CanonicalXML10CommentAlgorithmId: canonicalSignedInfo = canonicalPrep(detachedSignedInfo, map[string]struct{}{}, true) default: return fmt.Errorf("invalid CanonicalizationMethod on Signature: %s", c14NAlgorithm) } signatureEl.RemoveChild(signedInfo) signatureEl.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, 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 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) } goxmldsig-1.1.1/validate_test.go000066400000000000000000001710661411232664200167020ustar00rootroot00000000000000package dsig import ( "crypto/x509" "encoding/base64" "encoding/pem" "testing" "github.com/beevik/etree" "github.com/stretchr/testify/require" ) const canonicalResponse = ` http://www.okta.com/exk5zt0r12Edi4rD20h7http://www.okta.com/exk5zt0r12Edi4rD20h7DRYTp4xjc4Ec2+fJkQQ2KxFp/4raYPQYGrLtXTp2IhQ=UquJAMHALMZGSab+9XCc6L010djnsDx1wOP7b3LEQpEmGsKUEbblAuI1mdCaKi28VSP7h04S8M4x4xmgG6+RgYERKrMrc6DsW5Mto3nl6TaYQYUMVchp7vX1kDmuGqiEuYusrqIwQnFJNgt+SDAXODolfaJqKH02EMrzEeSFyfEiwaP8+R2jTQ9vqrMTX+t9b9nNo7F1N2sPWFGfk2TC3F5r4H+MF7n33cSny/qzPEEisldLF3LoTdnrPJdKpio/9kPr7ODhks+hwij82gYlvLCXkagmn76lSsAbUgsYoq1C3zvhYUHjTH2c0jmqHNwKT/8FA/oJtxx3N9agDpXEHw==MIIDpDCCAoygAwIBAgIGAVLIBhAwMA0GCSqGSIb3DQEBBQUAMIGSMQswCQYDVQQGEwJVUzETMBEG A1UECAwKQ2FsaWZvcm5pYTEWMBQGA1UEBwwNU2FuIEZyYW5jaXNjbzENMAsGA1UECgwET2t0YTEU MBIGA1UECwwLU1NPUHJvdmlkZXIxEzARBgNVBAMMCmRldi0xMTY4MDcxHDAaBgkqhkiG9w0BCQEW DWluZm9Ab2t0YS5jb20wHhcNMTYwMjA5MjE1MjA2WhcNMjYwMjA5MjE1MzA2WjCBkjELMAkGA1UE BhMCVVMxEzARBgNVBAgMCkNhbGlmb3JuaWExFjAUBgNVBAcMDVNhbiBGcmFuY2lzY28xDTALBgNV BAoMBE9rdGExFDASBgNVBAsMC1NTT1Byb3ZpZGVyMRMwEQYDVQQDDApkZXYtMTE2ODA3MRwwGgYJ KoZIhvcNAQkBFg1pbmZvQG9rdGEuY29tMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA mtjBOZ8MmhUyi8cGk4dUY6Fj1MFDt/q3FFiaQpLzu3/q5lRVUNUBbAtqQWwY10dzfZguHOuvA5p5 QyiVDvUhe+XkVwN2R2WfArQJRTPnIcOaHrxqQf3o5cCIG21ZtysFHJSo8clPSOe+0VsoRgcJ1aF4 2rODwgqRRZdO9Wh3502XlJ799DJQ23IC7XasKEsGKzJqhlRrfd/FyIuZT0sFHDKRz5snSJhm9gpN uQlCmk7ONZ1sXqtt+nBIfWIqeoYQubPW7pT5GTc7wouWq4TCjHJiK9k2HiyNxW0E3JX08swEZi2+ LVDjgLzNc4lwjSYIj3AOtPZs8s606oBdIBni4wIDAQABMA0GCSqGSIb3DQEBBQUAA4IBAQBMxSkJ TxkXxsoKNW0awJNpWRbU81QpheMFfENIzLam4Itc/5kSZAaSy/9e2QKfo4jBo/MMbCq2vM9TyeJQ DJpRaioUTd2lGh4TLUxAxCxtUk/pascL+3Nn936LFmUCLxaxnbeGzPOXAhscCtU1H0nFsXRnKx5a cPXYSKFZZZktieSkww2Oi8dg2DYaQhGQMSFMVqgVfwEu4bvCRBvdSiNXdWGCZQmFVzBZZ/9rOLzP pvTFTPnpkavJm81FLlUhiE/oFgKlCDLWDknSpXAI0uZGERcwPca6xvIMh86LjQKjbVci9FYDStXC qRnqQ+TccSu/B6uONFsDEngGcXSKfB+arussell.haering@scaleft.com123urn:oasis:names:tc:SAML:2.0:ac:classes:PasswordProtectedTransport` const canonicalResponse2 = ` http://www.okta.com/exk5zt0r12Edi4rD20h7http://www.okta.com/exk5zt0r12Edi4rD20h7No1VyQlk8Xif4FiJ+haViwEQySIzBa14lGy0coCn0c8=VSV8Vw47q7n/XZwaQOPWQeKI5ZA69fnGZyEFhex4xuaIfC+LOYnfd8q8qcZsm1M6kv47H/dR6YXRIMjPKXZeyX/MKcmGPCadqWFT7EWFvzuO/uy/AB/CL5ZCQiY9H/aOhDysO8glse1S+Y2K0CwvsoRwMfFiO2XOYhVOsngUSkCBdLIB6Oq4f+ZsK0rw/E79n9QUd8owDq3dVC18SFYYdcIVDhQppglyuBEZfu2tG06gD9jls7ZE8vjcMfHmhuHtxlH3ovNLB35NFO/VrCNdFqmD76GnEA98foiJxCX8vzNHF4rPUFXAEdiS4OdQAxb7jNNVoKVYuadunLygysZGSg==MIIDpDCCAoygAwIBAgIGAVLIBhAwMA0GCSqGSIb3DQEBBQUAMIGSMQswCQYDVQQGEwJVUzETMBEG A1UECAwKQ2FsaWZvcm5pYTEWMBQGA1UEBwwNU2FuIEZyYW5jaXNjbzENMAsGA1UECgwET2t0YTEU MBIGA1UECwwLU1NPUHJvdmlkZXIxEzARBgNVBAMMCmRldi0xMTY4MDcxHDAaBgkqhkiG9w0BCQEW DWluZm9Ab2t0YS5jb20wHhcNMTYwMjA5MjE1MjA2WhcNMjYwMjA5MjE1MzA2WjCBkjELMAkGA1UE BhMCVVMxEzARBgNVBAgMCkNhbGlmb3JuaWExFjAUBgNVBAcMDVNhbiBGcmFuY2lzY28xDTALBgNV BAoMBE9rdGExFDASBgNVBAsMC1NTT1Byb3ZpZGVyMRMwEQYDVQQDDApkZXYtMTE2ODA3MRwwGgYJ KoZIhvcNAQkBFg1pbmZvQG9rdGEuY29tMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA mtjBOZ8MmhUyi8cGk4dUY6Fj1MFDt/q3FFiaQpLzu3/q5lRVUNUBbAtqQWwY10dzfZguHOuvA5p5 QyiVDvUhe+XkVwN2R2WfArQJRTPnIcOaHrxqQf3o5cCIG21ZtysFHJSo8clPSOe+0VsoRgcJ1aF4 2rODwgqRRZdO9Wh3502XlJ799DJQ23IC7XasKEsGKzJqhlRrfd/FyIuZT0sFHDKRz5snSJhm9gpN uQlCmk7ONZ1sXqtt+nBIfWIqeoYQubPW7pT5GTc7wouWq4TCjHJiK9k2HiyNxW0E3JX08swEZi2+ LVDjgLzNc4lwjSYIj3AOtPZs8s606oBdIBni4wIDAQABMA0GCSqGSIb3DQEBBQUAA4IBAQBMxSkJ TxkXxsoKNW0awJNpWRbU81QpheMFfENIzLam4Itc/5kSZAaSy/9e2QKfo4jBo/MMbCq2vM9TyeJQ DJpRaioUTd2lGh4TLUxAxCxtUk/pascL+3Nn936LFmUCLxaxnbeGzPOXAhscCtU1H0nFsXRnKx5a cPXYSKFZZZktieSkww2Oi8dg2DYaQhGQMSFMVqgVfwEu4bvCRBvdSiNXdWGCZQmFVzBZZ/9rOLzP pvTFTPnpkavJm81FLlUhiE/oFgKlCDLWDknSpXAI0uZGERcwPca6xvIMh86LjQKjbVci9FYDStXC qRnqQ+TccSu/B6uONFsDEngGcXSKfB+arussell.haering@scaleft.com123urn:oasis:names:tc:SAML:2.0:ac:classes:PasswordProtectedTransport` const rawResponse = ` http://www.okta.com/exk5zt0r12Edi4rD20h7ijTqmVmDy7ssK+rvmJaCQ6AQaFaXz+HIN/r6O37B0eQ=G09fAYXGDLK+/jAekHsNL0RLo40Xm6+VwXmUj0IDIrvIIv/mJU5VD6ylOLnPezLDBVY9BJst1YCz+8krdvmQ8Stkd6qiN2bN/5KpCdika111YGpeNdMmg/E57ZG3S895hTNJQYOfCwhPFUtQuXLkspOaw81pcqOTr+bVSofJ8uQP7cVQa/ANxbjKAj0fhAuxAvZfiqPms5Stv4sNGpzULUDJl87CoEleHExGmpTsI7Qt3EvGToPMZXPHF4MGvuC0Z2ZD4iI6Pr7xk98t54PJtAX2qJu1tZqBJmL0Qcq5spl9W3yC1tAZuDeFLm1C4/T9crO2Q5WILP/tkw/yJ+ZttQ==MIIDpDCCAoygAwIBAgIGAVLIBhAwMA0GCSqGSIb3DQEBBQUAMIGSMQswCQYDVQQGEwJVUzETMBEG A1UECAwKQ2FsaWZvcm5pYTEWMBQGA1UEBwwNU2FuIEZyYW5jaXNjbzENMAsGA1UECgwET2t0YTEU MBIGA1UECwwLU1NPUHJvdmlkZXIxEzARBgNVBAMMCmRldi0xMTY4MDcxHDAaBgkqhkiG9w0BCQEW DWluZm9Ab2t0YS5jb20wHhcNMTYwMjA5MjE1MjA2WhcNMjYwMjA5MjE1MzA2WjCBkjELMAkGA1UE BhMCVVMxEzARBgNVBAgMCkNhbGlmb3JuaWExFjAUBgNVBAcMDVNhbiBGcmFuY2lzY28xDTALBgNV BAoMBE9rdGExFDASBgNVBAsMC1NTT1Byb3ZpZGVyMRMwEQYDVQQDDApkZXYtMTE2ODA3MRwwGgYJ KoZIhvcNAQkBFg1pbmZvQG9rdGEuY29tMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA mtjBOZ8MmhUyi8cGk4dUY6Fj1MFDt/q3FFiaQpLzu3/q5lRVUNUBbAtqQWwY10dzfZguHOuvA5p5 QyiVDvUhe+XkVwN2R2WfArQJRTPnIcOaHrxqQf3o5cCIG21ZtysFHJSo8clPSOe+0VsoRgcJ1aF4 2rODwgqRRZdO9Wh3502XlJ799DJQ23IC7XasKEsGKzJqhlRrfd/FyIuZT0sFHDKRz5snSJhm9gpN uQlCmk7ONZ1sXqtt+nBIfWIqeoYQubPW7pT5GTc7wouWq4TCjHJiK9k2HiyNxW0E3JX08swEZi2+ LVDjgLzNc4lwjSYIj3AOtPZs8s606oBdIBni4wIDAQABMA0GCSqGSIb3DQEBBQUAA4IBAQBMxSkJ TxkXxsoKNW0awJNpWRbU81QpheMFfENIzLam4Itc/5kSZAaSy/9e2QKfo4jBo/MMbCq2vM9TyeJQ DJpRaioUTd2lGh4TLUxAxCxtUk/pascL+3Nn936LFmUCLxaxnbeGzPOXAhscCtU1H0nFsXRnKx5a cPXYSKFZZZktieSkww2Oi8dg2DYaQhGQMSFMVqgVfwEu4bvCRBvdSiNXdWGCZQmFVzBZZ/9rOLzP pvTFTPnpkavJm81FLlUhiE/oFgKlCDLWDknSpXAI0uZGERcwPca6xvIMh86LjQKjbVci9FYDStXC qRnqQ+TccSu/B6uONFsDEngGcXSKfB+ahttp://www.okta.com/exk5zt0r12Edi4rD20h7zln6sheEO2JBdanrT5mZtJZ192tGHavuBpCFHQsJFVg=dHh6TWbnjtImyrfjPTX5QzE/6Vm/HsRWVvWWlvFAddf/CvhO4Kc5j8C7hvQoYMLhYuZMFFSReGysuDy5IscOJwTGhhcvb238qHSGGs6q8OUBCsmLSDAbIaGA++LV/tkUZ2ridGIi0yT81UOl1oT1batlHsK3eMyxkpnFmvBzIm4tGTzRkOPpYRLeiM9bxbKI+DM/623DCXyBCLYBzJo1O6QE02aLajwRMi/vmiV4LSiGlFcY9TtDCafdVJRv0tIQ25BQoT4feuHdr6S8xOSpGgRYH5ECamVOt4e079XdEkVUiSzQokiUkgDlTXEyerPLOVsOk4PW5nRs86sXIiGL5w==MIIDpDCCAoygAwIBAgIGAVLIBhAwMA0GCSqGSIb3DQEBBQUAMIGSMQswCQYDVQQGEwJVUzETMBEG A1UECAwKQ2FsaWZvcm5pYTEWMBQGA1UEBwwNU2FuIEZyYW5jaXNjbzENMAsGA1UECgwET2t0YTEU MBIGA1UECwwLU1NPUHJvdmlkZXIxEzARBgNVBAMMCmRldi0xMTY4MDcxHDAaBgkqhkiG9w0BCQEW DWluZm9Ab2t0YS5jb20wHhcNMTYwMjA5MjE1MjA2WhcNMjYwMjA5MjE1MzA2WjCBkjELMAkGA1UE BhMCVVMxEzARBgNVBAgMCkNhbGlmb3JuaWExFjAUBgNVBAcMDVNhbiBGcmFuY2lzY28xDTALBgNV BAoMBE9rdGExFDASBgNVBAsMC1NTT1Byb3ZpZGVyMRMwEQYDVQQDDApkZXYtMTE2ODA3MRwwGgYJ KoZIhvcNAQkBFg1pbmZvQG9rdGEuY29tMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA mtjBOZ8MmhUyi8cGk4dUY6Fj1MFDt/q3FFiaQpLzu3/q5lRVUNUBbAtqQWwY10dzfZguHOuvA5p5 QyiVDvUhe+XkVwN2R2WfArQJRTPnIcOaHrxqQf3o5cCIG21ZtysFHJSo8clPSOe+0VsoRgcJ1aF4 2rODwgqRRZdO9Wh3502XlJ799DJQ23IC7XasKEsGKzJqhlRrfd/FyIuZT0sFHDKRz5snSJhm9gpN uQlCmk7ONZ1sXqtt+nBIfWIqeoYQubPW7pT5GTc7wouWq4TCjHJiK9k2HiyNxW0E3JX08swEZi2+ LVDjgLzNc4lwjSYIj3AOtPZs8s606oBdIBni4wIDAQABMA0GCSqGSIb3DQEBBQUAA4IBAQBMxSkJ TxkXxsoKNW0awJNpWRbU81QpheMFfENIzLam4Itc/5kSZAaSy/9e2QKfo4jBo/MMbCq2vM9TyeJQ DJpRaioUTd2lGh4TLUxAxCxtUk/pascL+3Nn936LFmUCLxaxnbeGzPOXAhscCtU1H0nFsXRnKx5a cPXYSKFZZZktieSkww2Oi8dg2DYaQhGQMSFMVqgVfwEu4bvCRBvdSiNXdWGCZQmFVzBZZ/9rOLzP pvTFTPnpkavJm81FLlUhiE/oFgKlCDLWDknSpXAI0uZGERcwPca6xvIMh86LjQKjbVci9FYDStXC qRnqQ+TccSu/B6uONFsDEngGcXSKfB+aphoebe.simon@scaleft.com123urn:oasis:names:tc:SAML:2.0:ac:classes:PasswordProtectedTransportPhoebeSimonphoebe.simon@scaleft.com` const expectedTransformation = `http://www.okta.com/exk5zt0r12Edi4rD20h7http://www.okta.com/exk5zt0r12Edi4rD20h7zln6sheEO2JBdanrT5mZtJZ192tGHavuBpCFHQsJFVg=dHh6TWbnjtImyrfjPTX5QzE/6Vm/HsRWVvWWlvFAddf/CvhO4Kc5j8C7hvQoYMLhYuZMFFSReGysuDy5IscOJwTGhhcvb238qHSGGs6q8OUBCsmLSDAbIaGA++LV/tkUZ2ridGIi0yT81UOl1oT1batlHsK3eMyxkpnFmvBzIm4tGTzRkOPpYRLeiM9bxbKI+DM/623DCXyBCLYBzJo1O6QE02aLajwRMi/vmiV4LSiGlFcY9TtDCafdVJRv0tIQ25BQoT4feuHdr6S8xOSpGgRYH5ECamVOt4e079XdEkVUiSzQokiUkgDlTXEyerPLOVsOk4PW5nRs86sXIiGL5w==MIIDpDCCAoygAwIBAgIGAVLIBhAwMA0GCSqGSIb3DQEBBQUAMIGSMQswCQYDVQQGEwJVUzETMBEG A1UECAwKQ2FsaWZvcm5pYTEWMBQGA1UEBwwNU2FuIEZyYW5jaXNjbzENMAsGA1UECgwET2t0YTEU MBIGA1UECwwLU1NPUHJvdmlkZXIxEzARBgNVBAMMCmRldi0xMTY4MDcxHDAaBgkqhkiG9w0BCQEW DWluZm9Ab2t0YS5jb20wHhcNMTYwMjA5MjE1MjA2WhcNMjYwMjA5MjE1MzA2WjCBkjELMAkGA1UE BhMCVVMxEzARBgNVBAgMCkNhbGlmb3JuaWExFjAUBgNVBAcMDVNhbiBGcmFuY2lzY28xDTALBgNV BAoMBE9rdGExFDASBgNVBAsMC1NTT1Byb3ZpZGVyMRMwEQYDVQQDDApkZXYtMTE2ODA3MRwwGgYJ KoZIhvcNAQkBFg1pbmZvQG9rdGEuY29tMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA mtjBOZ8MmhUyi8cGk4dUY6Fj1MFDt/q3FFiaQpLzu3/q5lRVUNUBbAtqQWwY10dzfZguHOuvA5p5 QyiVDvUhe+XkVwN2R2WfArQJRTPnIcOaHrxqQf3o5cCIG21ZtysFHJSo8clPSOe+0VsoRgcJ1aF4 2rODwgqRRZdO9Wh3502XlJ799DJQ23IC7XasKEsGKzJqhlRrfd/FyIuZT0sFHDKRz5snSJhm9gpN uQlCmk7ONZ1sXqtt+nBIfWIqeoYQubPW7pT5GTc7wouWq4TCjHJiK9k2HiyNxW0E3JX08swEZi2+ LVDjgLzNc4lwjSYIj3AOtPZs8s606oBdIBni4wIDAQABMA0GCSqGSIb3DQEBBQUAA4IBAQBMxSkJ TxkXxsoKNW0awJNpWRbU81QpheMFfENIzLam4Itc/5kSZAaSy/9e2QKfo4jBo/MMbCq2vM9TyeJQ DJpRaioUTd2lGh4TLUxAxCxtUk/pascL+3Nn936LFmUCLxaxnbeGzPOXAhscCtU1H0nFsXRnKx5a cPXYSKFZZZktieSkww2Oi8dg2DYaQhGQMSFMVqgVfwEu4bvCRBvdSiNXdWGCZQmFVzBZZ/9rOLzP pvTFTPnpkavJm81FLlUhiE/oFgKlCDLWDknSpXAI0uZGERcwPca6xvIMh86LjQKjbVci9FYDStXC qRnqQ+TccSu/B6uONFsDEngGcXSKfB+aphoebe.simon@scaleft.com123urn:oasis:names:tc:SAML:2.0:ac:classes:PasswordProtectedTransportPhoebeSimonphoebe.simon@scaleft.com` const emptyReference = `a1sl6AXnoU1CaZSx2MuDPLSKWAhGd6K40pcXe502u+Zw=jvr8AB4NzTi6FpZV27m6tsWtUXu4kPcCgx3vzE/T0om+DzOs0pkXhTD0H3oNqoWFOnpUo2dqO26nR58hzNpcIHPJPrHnNfboZJf68btzMNDa/OlnFtuwbFWo8Ac+rXS/Up3X5B3CNRlTz/W+ALZEuUHBGNZjE0Hw9Aav8YKAxiWx6uA9z0CCXUFVCbjmtrISMPSUQio+KjIc50j7BbVcezWTz/QB/ySsLEp/Zl4vCTCStFIkdZR/h3Ha5jovxsxuzERZ09x0l748dp8Cm449RnqOz4TIinxKz0xkqtFnbFmF1rFiGF8Vha2f7mdUqgmuy4ifevSI7G2ZQae3vQoNbw==MIIDPDCCAiQCCQDydJgOlszqbzANBgkqhkiG9w0BAQUFADBgMQswCQYDVQQGEwJVUzETMBEGA1UECBMKQ2FsaWZvcm5pYTEWMBQGA1UEBxMNU2FuIEZyYW5jaXNjbzEQMA4GA1UEChMHSmFua3lDbzESMBAGA1UEAxMJbG9jYWxob3N0MB4XDTE0MDMxMjE5NDYzM1oXDTI3MTExOTE5NDYzM1owYDELMAkGA1UEBhMCVVMxEzARBgNVBAgTCkNhbGlmb3JuaWExFjAUBgNVBAcTDVNhbiBGcmFuY2lzY28xEDAOBgNVBAoTB0phbmt5Q28xEjAQBgNVBAMTCWxvY2FsaG9zdDCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAMGvJpRTTasRUSPqcbqCG+ZnTAurnu0vVpIG9lzExnh11o/BGmzu7lB+yLHcEdwrKBBmpepDBPCYxpVajvuEhZdKFx/Fdy6j5mH3rrW0Bh/zd36CoUNjbbhHyTjeM7FN2yF3u9lcyubuvOzr3B3gX66IwJlU46+wzcQVhSOlMk2tXR+fIKQExFrOuK9tbX3JIBUqItpI+HnAow509CnM134svw8PTFLkR6/CcMqnDfDK1m993PyoC1Y+N4X9XkhSmEQoAlAHPI5LHrvuujM13nvtoVYvKYoj7ScgumkpWNEvX652LfXOnKYlkB8ZybuxmFfIkzedQrbJsyOhfL03cMECAwEAATANBgkqhkiG9w0BAQUFAAOCAQEAeHwzqwnzGEkxjzSD47imXaTqtYyETZow7XwBc0ZaFS50qRFJUgKTAmKS1xQBP/qHpStsROT35DUxJAE6NY1Kbq3ZbCuhGoSlY0L7VzVT5tpu4EY8+Dq/u2EjRmmhoL7UkskvIZ2n1DdERtd+YUMTeqYl9co43csZwDno/IKomeN5qaPc39IZjikJ+nUC6kPFKeu/3j9rgHNlRtocI6S1FdtFz9OZMQlpr0JbUt2T3xS/YoQJn6coDmJL5GTiiKM6cOe+Ur1VwzS1JEDbSS2TWWhzq8ojLdrotYLGd9JOsoQhElmz+tMfCFQUFLExinPAyy7YHlSiVX13QH2XTu/iQQ==aarun+okta@launchdarkly.combarun+okta@launchdarkly.comarun+okta@launchdarkly.comArunBhallaurn:oasis:names:tc:SAML:2.0:ac:classes:unspecified` const oktaCert = ` -----BEGIN CERTIFICATE----- MIIDPDCCAiQCCQDydJgOlszqbzANBgkqhkiG9w0BAQUFADBgMQswCQYDVQQGEwJV UzETMBEGA1UECBMKQ2FsaWZvcm5pYTEWMBQGA1UEBxMNU2FuIEZyYW5jaXNjbzEQ MA4GA1UEChMHSmFua3lDbzESMBAGA1UEAxMJbG9jYWxob3N0MB4XDTE0MDMxMjE5 NDYzM1oXDTI3MTExOTE5NDYzM1owYDELMAkGA1UEBhMCVVMxEzARBgNVBAgTCkNh bGlmb3JuaWExFjAUBgNVBAcTDVNhbiBGcmFuY2lzY28xEDAOBgNVBAoTB0phbmt5 Q28xEjAQBgNVBAMTCWxvY2FsaG9zdDCCASIwDQYJKoZIhvcNAQEBBQADggEPADCC AQoCggEBAMGvJpRTTasRUSPqcbqCG+ZnTAurnu0vVpIG9lzExnh11o/BGmzu7lB+ yLHcEdwrKBBmpepDBPCYxpVajvuEhZdKFx/Fdy6j5mH3rrW0Bh/zd36CoUNjbbhH yTjeM7FN2yF3u9lcyubuvOzr3B3gX66IwJlU46+wzcQVhSOlMk2tXR+fIKQExFrO uK9tbX3JIBUqItpI+HnAow509CnM134svw8PTFLkR6/CcMqnDfDK1m993PyoC1Y+ N4X9XkhSmEQoAlAHPI5LHrvuujM13nvtoVYvKYoj7ScgumkpWNEvX652LfXOnKYl kB8ZybuxmFfIkzedQrbJsyOhfL03cMECAwEAATANBgkqhkiG9w0BAQUFAAOCAQEA eHwzqwnzGEkxjzSD47imXaTqtYyETZow7XwBc0ZaFS50qRFJUgKTAmKS1xQBP/qH pStsROT35DUxJAE6NY1Kbq3ZbCuhGoSlY0L7VzVT5tpu4EY8+Dq/u2EjRmmhoL7U kskvIZ2n1DdERtd+YUMTeqYl9co43csZwDno/IKomeN5qaPc39IZjikJ+nUC6kPF Keu/3j9rgHNlRtocI6S1FdtFz9OZMQlpr0JbUt2T3xS/YoQJn6coDmJL5GTiiKM6 cOe+Ur1VwzS1JEDbSS2TWWhzq8ojLdrotYLGd9JOsoQhElmz+tMfCFQUFLExinPA yy7YHlSiVX13QH2XTu/iQQ== -----END CERTIFICATE----- ` func TestDigest(t *testing.T) { canonicalizer := MakeC14N10ExclusiveCanonicalizerWithPrefixList("") doc := etree.NewDocument() err := doc.ReadFromBytes([]byte(canonicalResponse)) require.NoError(t, err) vc := NewDefaultValidationContext(nil) digest, err := vc.digest(doc.Root(), "http://www.w3.org/2001/04/xmlenc#sha256", canonicalizer) require.NoError(t, err) require.Equal(t, "gvXF2ygtu4WbVYdepEtHFbgCZLfKW893eFF+x6gjX80=", base64.StdEncoding.EncodeToString(digest)) doc = etree.NewDocument() err = doc.ReadFromBytes([]byte(canonicalResponse2)) require.NoError(t, err) vc = NewDefaultValidationContext(nil) digest, err = vc.digest(doc.Root(), "http://www.w3.org/2001/04/xmlenc#sha256", canonicalizer) require.NoError(t, err) require.Equal(t, "npTAl6kraksBlCRlunbyD6nICTcfsDaHjPXVxoDPrw0=", base64.StdEncoding.EncodeToString(digest)) } func TestTransform(t *testing.T) { doc := etree.NewDocument() err := doc.ReadFromBytes([]byte(rawResponse)) require.NoError(t, err) vc := NewDefaultValidationContext(nil) el := doc.Root() sig, err := vc.findSignature(el) require.NoError(t, err) ref := &sig.SignedInfo.References[0] transformed, canonicalizer, err := vc.transform(el, sig, ref) require.NoError(t, err) require.NotEmpty(t, transformed) require.IsType(t, &c14N10ExclusiveCanonicalizer{}, canonicalizer) doc = etree.NewDocument() doc.SetRoot(transformed) str, err := doc.WriteToString() require.NoError(t, err) require.Equal(t, expectedTransformation, str) } func TestValidateWithEmptySignatureReference(t *testing.T) { doc := etree.NewDocument() err := doc.ReadFromBytes([]byte(emptyReference)) require.NoError(t, err) sig := doc.FindElement("//" + SignatureTag) require.NotEmpty(t, sig) // Verify that Reference URI is empty signedInfo := sig.FindElement(childPath(sig.Space, SignedInfoTag)) require.NotEmpty(t, signedInfo) reference := signedInfo.FindElement(childPath(sig.Space, ReferenceTag)) require.NotEmpty(t, reference) require.Empty(t, reference.SelectAttr(URIAttr).Value) block, _ := pem.Decode([]byte(oktaCert)) cert, err := x509.ParseCertificate(block.Bytes) require.NoError(t, err, "couldn't parse okta cert pem block") certStore := MemoryX509CertificateStore{ Roots: []*x509.Certificate{cert}, } vc := NewDefaultValidationContext(&certStore) el, err := vc.Validate(doc.Root()) require.NoError(t, err) require.NotEmpty(t, el) } const ( validateCert = ` -----BEGIN CERTIFICATE----- MIIDnjCCAoagAwIBAgIGAXHxS90vMA0GCSqGSIb3DQEBCwUAMIGPMQswCQYDVQQG EwJVUzETMBEGA1UECAwKQ2FsaWZvcm5pYTEWMBQGA1UEBwwNU2FuIEZyYW5jaXNj bzENMAsGA1UECgwET2t0YTEUMBIGA1UECwwLU1NPUHJvdmlkZXIxEDAOBgNVBAMM B2FzYS1kZXYxHDAaBgkqhkiG9w0BCQEWDWluZm9Ab2t0YS5jb20wHhcNMjAwNTA3 MjIzOTEzWhcNMzAwNTA3MjI0MDEzWjCBjzELMAkGA1UEBhMCVVMxEzARBgNVBAgM CkNhbGlmb3JuaWExFjAUBgNVBAcMDVNhbiBGcmFuY2lzY28xDTALBgNVBAoMBE9r dGExFDASBgNVBAsMC1NTT1Byb3ZpZGVyMRAwDgYDVQQDDAdhc2EtZGV2MRwwGgYJ KoZIhvcNAQkBFg1pbmZvQG9rdGEuY29tMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8A MIIBCgKCAQEAqlQF++AiiKrOb5MVwN8YEgFCbOdLSO44hcJq2BYZYRd1oq1XVnz7 fVC49YgPXRafpXJx4v8jWyRQug2Sv4nEMvsbVzrV9N09/RHQ1MVa4QlTUEAhR0nS zs897k2e6zObf/zx5ugE+GLx03+chYFVv1ICup0e0pRNS6OWHYFzZnLTlCEgAbay HkbA82EViqgWD53BNQLvsS06WztF4pGISyxZ2NpycV5ejmI3ZSr6+bKXcgNAWr7i nNBUaOwJG52/NlBAKaMq56Bljsni6YmZ/9V2DbQgTHSn4mu+++4FdDtFxBe1ZPID JpjguXf9X183H7ZIkNOxkr+YlW02uzOpBQIDAQABMA0GCSqGSIb3DQEBCwUAA4IB AQBRX6NORxMS4cDWkG/PqlYcCjgwZA/8rd6dBkI+wJEzqrXmO1SSIQW6F48ahDVq T0nicDYSnTkplIbKmooKjm2kkuCIjLwDiLldpZZ/Hpdj9rGDLC2jS6m3dr6OQvoT DYPOXfrgMykc5VM+h9yx+iYbrilmmrhOwIPxxZDVUiRSB6Op716xk+9d0jlyrtFF 77B3YlKgMThQG6rguXViSwmViywWx+UQD6F1OzES8hoL54hfriOnlIpzZeamtJCo /jcdeqYHi3ru+uHOBe91GFPtoDGCVuk7YvzlXKMdgyDx82+kRSnLWYMxaI2zleFY nXHhoQk3K5iSdQT/gFgKJk89 -----END CERTIFICATE-----` ) func TestValidateWithValid(t *testing.T) { doc := etree.NewDocument() err := doc.ReadFromBytes([]byte(validExample)) require.NoError(t, err) block, _ := pem.Decode([]byte(validateCert)) cert, err := x509.ParseCertificate(block.Bytes) require.NoError(t, err, "couldn't parse okta cert pem block") certStore := MemoryX509CertificateStore{ Roots: []*x509.Certificate{cert}, } vc := NewDefaultValidationContext(&certStore) el, err := vc.Validate(doc.Root()) require.NoError(t, err) require.NotEmpty(t, el) } func TestValidateWithModified(t *testing.T) { doc := etree.NewDocument() err := doc.ReadFromBytes([]byte(modifiedToBeTodd)) require.NoError(t, err) block, _ := pem.Decode([]byte(validateCert)) cert, err := x509.ParseCertificate(block.Bytes) require.NoError(t, err, "couldn't parse okta cert pem block") certStore := MemoryX509CertificateStore{ Roots: []*x509.Certificate{cert}, } vc := NewDefaultValidationContext(&certStore) _, err = vc.Validate(doc.Root()) require.Error(t, err) } func TestValidateWithModifiedAndSignatureEdited(t *testing.T) { doc := etree.NewDocument() err := doc.ReadFromBytes([]byte(spoofedAsTodd)) require.NoError(t, err) block, _ := pem.Decode([]byte(validateCert)) cert, err := x509.ParseCertificate(block.Bytes) require.NoError(t, err, "couldn't parse okta cert pem block") certStore := MemoryX509CertificateStore{ Roots: []*x509.Certificate{cert}, } vc := NewDefaultValidationContext(&certStore) _, err = vc.Validate(doc.Root()) require.Error(t, err) } const ( validExample = `http://www.okta.com/exkrfkzzb7NyB3UeP0h7LwRDkrPmsTcUa++BIS5VJIANUlZN7zzdtjLfxfLAWds=UyjNRj9ZFbhApPhWEuVG26yACVqd25uyRKalSpp6XCdjrqKjI8Fmx7Q/IFkk5M755cxyFCQGttxThR6IPBk4Kp5OG2qGKXNHt7OQ8mumSLqWZpBJbmzNIKyG3nWlFoLVCoWPtBTd2gZM0aHOQp1JKa1birFBp2NofkEXbLeghZQ2YfCc4m8qgpZW5k/Itc0P/TVIkvPInjdSMyjm/ql4FUDO8cMkExJNR/i+GElW8cfnniWGcDPSiOqfIjLEDvZouXC7F1v5Wa0SmIxg7NJUTB+g6yrDN15VDq3KbHHTMlZXOZTXON2mBZOj5cwyyd4uX3aGSmYQiy/CGqBdqxrW2A==MIIDnjCCAoagAwIBAgIGAXHxS90vMA0GCSqGSIb3DQEBCwUAMIGPMQswCQYDVQQGEwJVUzETMBEG A1UECAwKQ2FsaWZvcm5pYTEWMBQGA1UEBwwNU2FuIEZyYW5jaXNjbzENMAsGA1UECgwET2t0YTEU MBIGA1UECwwLU1NPUHJvdmlkZXIxEDAOBgNVBAMMB2FzYS1kZXYxHDAaBgkqhkiG9w0BCQEWDWlu Zm9Ab2t0YS5jb20wHhcNMjAwNTA3MjIzOTEzWhcNMzAwNTA3MjI0MDEzWjCBjzELMAkGA1UEBhMC VVMxEzARBgNVBAgMCkNhbGlmb3JuaWExFjAUBgNVBAcMDVNhbiBGcmFuY2lzY28xDTALBgNVBAoM BE9rdGExFDASBgNVBAsMC1NTT1Byb3ZpZGVyMRAwDgYDVQQDDAdhc2EtZGV2MRwwGgYJKoZIhvcN AQkBFg1pbmZvQG9rdGEuY29tMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAqlQF++Ai iKrOb5MVwN8YEgFCbOdLSO44hcJq2BYZYRd1oq1XVnz7fVC49YgPXRafpXJx4v8jWyRQug2Sv4nE MvsbVzrV9N09/RHQ1MVa4QlTUEAhR0nSzs897k2e6zObf/zx5ugE+GLx03+chYFVv1ICup0e0pRN S6OWHYFzZnLTlCEgAbayHkbA82EViqgWD53BNQLvsS06WztF4pGISyxZ2NpycV5ejmI3ZSr6+bKX cgNAWr7inNBUaOwJG52/NlBAKaMq56Bljsni6YmZ/9V2DbQgTHSn4mu+++4FdDtFxBe1ZPIDJpjg uXf9X183H7ZIkNOxkr+YlW02uzOpBQIDAQABMA0GCSqGSIb3DQEBCwUAA4IBAQBRX6NORxMS4cDW kG/PqlYcCjgwZA/8rd6dBkI+wJEzqrXmO1SSIQW6F48ahDVqT0nicDYSnTkplIbKmooKjm2kkuCI jLwDiLldpZZ/Hpdj9rGDLC2jS6m3dr6OQvoTDYPOXfrgMykc5VM+h9yx+iYbrilmmrhOwIPxxZDV UiRSB6Op716xk+9d0jlyrtFF77B3YlKgMThQG6rguXViSwmViywWx+UQD6F1OzES8hoL54hfriOn lIpzZeamtJCo/jcdeqYHi3ru+uHOBe91GFPtoDGCVuk7YvzlXKMdgyDx82+kRSnLWYMxaI2zleFY nXHhoQk3K5iSdQT/gFgKJk89http://www.okta.com/exkrfkzzb7NyB3UeP0h7nrIzAXSDsFwgvCm+ulbqfqZylzPxCBof6FYDcCEPdCQ=en3gX+6oIzNnkUWPbIAZp3rX8kHelobV3qqNSQ/JXQAZX7Up42D1pU6dWNc68xLe7RCDr3xV6zFG2bpi+NyZlsmqyKIXot5W6cM0BKkmRxQDcR1ThwP/VrFQ2HRxKTDUNeNCkTGBDfbwyD+w9RuCZO5JP2DX7DBHFBaTQQ+/9EhPSEx6yvJ05CwJ8eoNd/0ib+FCF1VDn9haP0viA8cOg3ApMkpwJsPXvMpb6U/q1tGgtzcyvqYDfAkWYGG0YPk3BsTUhSa7dN/ZI6O+7ZDGtWQohhYCAXBShrM7OWwJBDA5J+AXo7wFWKMt36u+MqGu2hBC58t7NpkZXehBRhvmmg==MIIDnjCCAoagAwIBAgIGAXHxS90vMA0GCSqGSIb3DQEBCwUAMIGPMQswCQYDVQQGEwJVUzETMBEG A1UECAwKQ2FsaWZvcm5pYTEWMBQGA1UEBwwNU2FuIEZyYW5jaXNjbzENMAsGA1UECgwET2t0YTEU MBIGA1UECwwLU1NPUHJvdmlkZXIxEDAOBgNVBAMMB2FzYS1kZXYxHDAaBgkqhkiG9w0BCQEWDWlu Zm9Ab2t0YS5jb20wHhcNMjAwNTA3MjIzOTEzWhcNMzAwNTA3MjI0MDEzWjCBjzELMAkGA1UEBhMC VVMxEzARBgNVBAgMCkNhbGlmb3JuaWExFjAUBgNVBAcMDVNhbiBGcmFuY2lzY28xDTALBgNVBAoM BE9rdGExFDASBgNVBAsMC1NTT1Byb3ZpZGVyMRAwDgYDVQQDDAdhc2EtZGV2MRwwGgYJKoZIhvcN AQkBFg1pbmZvQG9rdGEuY29tMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAqlQF++Ai iKrOb5MVwN8YEgFCbOdLSO44hcJq2BYZYRd1oq1XVnz7fVC49YgPXRafpXJx4v8jWyRQug2Sv4nE MvsbVzrV9N09/RHQ1MVa4QlTUEAhR0nSzs897k2e6zObf/zx5ugE+GLx03+chYFVv1ICup0e0pRN S6OWHYFzZnLTlCEgAbayHkbA82EViqgWD53BNQLvsS06WztF4pGISyxZ2NpycV5ejmI3ZSr6+bKX cgNAWr7inNBUaOwJG52/NlBAKaMq56Bljsni6YmZ/9V2DbQgTHSn4mu+++4FdDtFxBe1ZPIDJpjg uXf9X183H7ZIkNOxkr+YlW02uzOpBQIDAQABMA0GCSqGSIb3DQEBCwUAA4IBAQBRX6NORxMS4cDW kG/PqlYcCjgwZA/8rd6dBkI+wJEzqrXmO1SSIQW6F48ahDVqT0nicDYSnTkplIbKmooKjm2kkuCI jLwDiLldpZZ/Hpdj9rGDLC2jS6m3dr6OQvoTDYPOXfrgMykc5VM+h9yx+iYbrilmmrhOwIPxxZDV UiRSB6Op716xk+9d0jlyrtFF77B3YlKgMThQG6rguXViSwmViywWx+UQD6F1OzES8hoL54hfriOn lIpzZeamtJCo/jcdeqYHi3ru+uHOBe91GFPtoDGCVuk7YvzlXKMdgyDx82+kRSnLWYMxaI2zleFY nXHhoQk3K5iSdQT/gFgKJk89phoebe.yu@okta.comhttps://dev.sudo.wtf:8443/v1/teams/asaurn:oasis:names:tc:SAML:2.0:ac:classes:PasswordProtectedTransportPhoebeYuphoebe.yu@okta.comphoebe.yu@okta.com` modifiedToBeTodd = `http://www.okta.com/exkrfkzzb7NyB3UeP0h7LwRDkrPmsTcUa++BIS5VJIANUlZN7zzdtjLfxfLAWds=UyjNRj9ZFbhApPhWEuVG26yACVqd25uyRKalSpp6XCdjrqKjI8Fmx7Q/IFkk5M755cxyFCQGttxThR6IPBk4Kp5OG2qGKXNHt7OQ8mumSLqWZpBJbmzNIKyG3nWlFoLVCoWPtBTd2gZM0aHOQp1JKa1birFBp2NofkEXbLeghZQ2YfCc4m8qgpZW5k/Itc0P/TVIkvPInjdSMyjm/ql4FUDO8cMkExJNR/i+GElW8cfnniWGcDPSiOqfIjLEDvZouXC7F1v5Wa0SmIxg7NJUTB+g6yrDN15VDq3KbHHTMlZXOZTXON2mBZOj5cwyyd4uX3aGSmYQiy/CGqBdqxrW2A==MIIDnjCCAoagAwIBAgIGAXHxS90vMA0GCSqGSIb3DQEBCwUAMIGPMQswCQYDVQQGEwJVUzETMBEG A1UECAwKQ2FsaWZvcm5pYTEWMBQGA1UEBwwNU2FuIEZyYW5jaXNjbzENMAsGA1UECgwET2t0YTEU MBIGA1UECwwLU1NPUHJvdmlkZXIxEDAOBgNVBAMMB2FzYS1kZXYxHDAaBgkqhkiG9w0BCQEWDWlu Zm9Ab2t0YS5jb20wHhcNMjAwNTA3MjIzOTEzWhcNMzAwNTA3MjI0MDEzWjCBjzELMAkGA1UEBhMC VVMxEzARBgNVBAgMCkNhbGlmb3JuaWExFjAUBgNVBAcMDVNhbiBGcmFuY2lzY28xDTALBgNVBAoM BE9rdGExFDASBgNVBAsMC1NTT1Byb3ZpZGVyMRAwDgYDVQQDDAdhc2EtZGV2MRwwGgYJKoZIhvcN AQkBFg1pbmZvQG9rdGEuY29tMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAqlQF++Ai iKrOb5MVwN8YEgFCbOdLSO44hcJq2BYZYRd1oq1XVnz7fVC49YgPXRafpXJx4v8jWyRQug2Sv4nE MvsbVzrV9N09/RHQ1MVa4QlTUEAhR0nSzs897k2e6zObf/zx5ugE+GLx03+chYFVv1ICup0e0pRN S6OWHYFzZnLTlCEgAbayHkbA82EViqgWD53BNQLvsS06WztF4pGISyxZ2NpycV5ejmI3ZSr6+bKX cgNAWr7inNBUaOwJG52/NlBAKaMq56Bljsni6YmZ/9V2DbQgTHSn4mu+++4FdDtFxBe1ZPIDJpjg uXf9X183H7ZIkNOxkr+YlW02uzOpBQIDAQABMA0GCSqGSIb3DQEBCwUAA4IBAQBRX6NORxMS4cDW kG/PqlYcCjgwZA/8rd6dBkI+wJEzqrXmO1SSIQW6F48ahDVqT0nicDYSnTkplIbKmooKjm2kkuCI jLwDiLldpZZ/Hpdj9rGDLC2jS6m3dr6OQvoTDYPOXfrgMykc5VM+h9yx+iYbrilmmrhOwIPxxZDV UiRSB6Op716xk+9d0jlyrtFF77B3YlKgMThQG6rguXViSwmViywWx+UQD6F1OzES8hoL54hfriOn lIpzZeamtJCo/jcdeqYHi3ru+uHOBe91GFPtoDGCVuk7YvzlXKMdgyDx82+kRSnLWYMxaI2zleFY nXHhoQk3K5iSdQT/gFgKJk89http://www.okta.com/exkrfkzzb7NyB3UeP0h7nrIzAXSDsFwgvCm+ulbqfqZylzPxCBof6FYDcCEPdCQ=en3gX+6oIzNnkUWPbIAZp3rX8kHelobV3qqNSQ/JXQAZX7Up42D1pU6dWNc68xLe7RCDr3xV6zFG2bpi+NyZlsmqyKIXot5W6cM0BKkmRxQDcR1ThwP/VrFQ2HRxKTDUNeNCkTGBDfbwyD+w9RuCZO5JP2DX7DBHFBaTQQ+/9EhPSEx6yvJ05CwJ8eoNd/0ib+FCF1VDn9haP0viA8cOg3ApMkpwJsPXvMpb6U/q1tGgtzcyvqYDfAkWYGG0YPk3BsTUhSa7dN/ZI6O+7ZDGtWQohhYCAXBShrM7OWwJBDA5J+AXo7wFWKMt36u+MqGu2hBC58t7NpkZXehBRhvmmg==MIIDnjCCAoagAwIBAgIGAXHxS90vMA0GCSqGSIb3DQEBCwUAMIGPMQswCQYDVQQGEwJVUzETMBEG A1UECAwKQ2FsaWZvcm5pYTEWMBQGA1UEBwwNU2FuIEZyYW5jaXNjbzENMAsGA1UECgwET2t0YTEU MBIGA1UECwwLU1NPUHJvdmlkZXIxEDAOBgNVBAMMB2FzYS1kZXYxHDAaBgkqhkiG9w0BCQEWDWlu Zm9Ab2t0YS5jb20wHhcNMjAwNTA3MjIzOTEzWhcNMzAwNTA3MjI0MDEzWjCBjzELMAkGA1UEBhMC VVMxEzARBgNVBAgMCkNhbGlmb3JuaWExFjAUBgNVBAcMDVNhbiBGcmFuY2lzY28xDTALBgNVBAoM BE9rdGExFDASBgNVBAsMC1NTT1Byb3ZpZGVyMRAwDgYDVQQDDAdhc2EtZGV2MRwwGgYJKoZIhvcN AQkBFg1pbmZvQG9rdGEuY29tMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAqlQF++Ai iKrOb5MVwN8YEgFCbOdLSO44hcJq2BYZYRd1oq1XVnz7fVC49YgPXRafpXJx4v8jWyRQug2Sv4nE MvsbVzrV9N09/RHQ1MVa4QlTUEAhR0nSzs897k2e6zObf/zx5ugE+GLx03+chYFVv1ICup0e0pRN S6OWHYFzZnLTlCEgAbayHkbA82EViqgWD53BNQLvsS06WztF4pGISyxZ2NpycV5ejmI3ZSr6+bKX cgNAWr7inNBUaOwJG52/NlBAKaMq56Bljsni6YmZ/9V2DbQgTHSn4mu+++4FdDtFxBe1ZPIDJpjg uXf9X183H7ZIkNOxkr+YlW02uzOpBQIDAQABMA0GCSqGSIb3DQEBCwUAA4IBAQBRX6NORxMS4cDW kG/PqlYcCjgwZA/8rd6dBkI+wJEzqrXmO1SSIQW6F48ahDVqT0nicDYSnTkplIbKmooKjm2kkuCI jLwDiLldpZZ/Hpdj9rGDLC2jS6m3dr6OQvoTDYPOXfrgMykc5VM+h9yx+iYbrilmmrhOwIPxxZDV UiRSB6Op716xk+9d0jlyrtFF77B3YlKgMThQG6rguXViSwmViywWx+UQD6F1OzES8hoL54hfriOn lIpzZeamtJCo/jcdeqYHi3ru+uHOBe91GFPtoDGCVuk7YvzlXKMdgyDx82+kRSnLWYMxaI2zleFY nXHhoQk3K5iSdQT/gFgKJk89todd@okta.comhttps://dev.sudo.wtf:8443/v1/teams/asaurn:oasis:names:tc:SAML:2.0:ac:classes:PasswordProtectedTransportPhoebeYuphoebe.yu@okta.comphoebe.yu@okta.com` spoofedAsTodd = `http://www.okta.com/exkrfkzzb7NyB3UeP0h7UyjNRj9ZFbhApPhWEuVG26yACVqd25uyRKalSpp6XCdjrqKjI8Fmx7Q/IFkk5M755cxyFCQGttxThR6IPBk4Kp5OG2qGKXNHt7OQ8mumSLqWZpBJbmzNIKyG3nWlFoLVCoWPtBTd2gZM0aHOQp1JKa1birFBp2NofkEXbLeghZQ2YfCc4m8qgpZW5k/Itc0P/TVIkvPInjdSMyjm/ql4FUDO8cMkExJNR/i+GElW8cfnniWGcDPSiOqfIjLEDvZouXC7F1v5Wa0SmIxg7NJUTB+g6yrDN15VDq3KbHHTMlZXOZTXON2mBZOj5cwyyd4uX3aGSmYQiy/CGqBdqxrW2A==MIIDnjCCAoagAwIBAgIGAXHxS90vMA0GCSqGSIb3DQEBCwUAMIGPMQswCQYDVQQGEwJVUzETMBEG A1UECAwKQ2FsaWZvcm5pYTEWMBQGA1UEBwwNU2FuIEZyYW5jaXNjbzENMAsGA1UECgwET2t0YTEU MBIGA1UECwwLU1NPUHJvdmlkZXIxEDAOBgNVBAMMB2FzYS1kZXYxHDAaBgkqhkiG9w0BCQEWDWlu Zm9Ab2t0YS5jb20wHhcNMjAwNTA3MjIzOTEzWhcNMzAwNTA3MjI0MDEzWjCBjzELMAkGA1UEBhMC VVMxEzARBgNVBAgMCkNhbGlmb3JuaWExFjAUBgNVBAcMDVNhbiBGcmFuY2lzY28xDTALBgNVBAoM BE9rdGExFDASBgNVBAsMC1NTT1Byb3ZpZGVyMRAwDgYDVQQDDAdhc2EtZGV2MRwwGgYJKoZIhvcN AQkBFg1pbmZvQG9rdGEuY29tMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAqlQF++Ai iKrOb5MVwN8YEgFCbOdLSO44hcJq2BYZYRd1oq1XVnz7fVC49YgPXRafpXJx4v8jWyRQug2Sv4nE MvsbVzrV9N09/RHQ1MVa4QlTUEAhR0nSzs897k2e6zObf/zx5ugE+GLx03+chYFVv1ICup0e0pRN S6OWHYFzZnLTlCEgAbayHkbA82EViqgWD53BNQLvsS06WztF4pGISyxZ2NpycV5ejmI3ZSr6+bKX cgNAWr7inNBUaOwJG52/NlBAKaMq56Bljsni6YmZ/9V2DbQgTHSn4mu+++4FdDtFxBe1ZPIDJpjg uXf9X183H7ZIkNOxkr+YlW02uzOpBQIDAQABMA0GCSqGSIb3DQEBCwUAA4IBAQBRX6NORxMS4cDW kG/PqlYcCjgwZA/8rd6dBkI+wJEzqrXmO1SSIQW6F48ahDVqT0nicDYSnTkplIbKmooKjm2kkuCI jLwDiLldpZZ/Hpdj9rGDLC2jS6m3dr6OQvoTDYPOXfrgMykc5VM+h9yx+iYbrilmmrhOwIPxxZDV UiRSB6Op716xk+9d0jlyrtFF77B3YlKgMThQG6rguXViSwmViywWx+UQD6F1OzES8hoL54hfriOn lIpzZeamtJCo/jcdeqYHi3ru+uHOBe91GFPtoDGCVuk7YvzlXKMdgyDx82+kRSnLWYMxaI2zleFY nXHhoQk3K5iSdQT/gFgKJk89cFNZV8Uh+WR8yPmPqAfktrxO0DtlNv7WIVf/iQbuwR0=LwRDkrPmsTcUa++BIS5VJIANUlZN7zzdtjLfxfLAWds=http://www.okta.com/exkrfkzzb7NyB3UeP0h7en3gX+6oIzNnkUWPbIAZp3rX8kHelobV3qqNSQ/JXQAZX7Up42D1pU6dWNc68xLe7RCDr3xV6zFG2bpi+NyZlsmqyKIXot5W6cM0BKkmRxQDcR1ThwP/VrFQ2HRxKTDUNeNCkTGBDfbwyD+w9RuCZO5JP2DX7DBHFBaTQQ+/9EhPSEx6yvJ05CwJ8eoNd/0ib+FCF1VDn9haP0viA8cOg3ApMkpwJsPXvMpb6U/q1tGgtzcyvqYDfAkWYGG0YPk3BsTUhSa7dN/ZI6O+7ZDGtWQohhYCAXBShrM7OWwJBDA5J+AXo7wFWKMt36u+MqGu2hBC58t7NpkZXehBRhvmmg==MIIDnjCCAoagAwIBAgIGAXHxS90vMA0GCSqGSIb3DQEBCwUAMIGPMQswCQYDVQQGEwJVUzETMBEG A1UECAwKQ2FsaWZvcm5pYTEWMBQGA1UEBwwNU2FuIEZyYW5jaXNjbzENMAsGA1UECgwET2t0YTEU MBIGA1UECwwLU1NPUHJvdmlkZXIxEDAOBgNVBAMMB2FzYS1kZXYxHDAaBgkqhkiG9w0BCQEWDWlu Zm9Ab2t0YS5jb20wHhcNMjAwNTA3MjIzOTEzWhcNMzAwNTA3MjI0MDEzWjCBjzELMAkGA1UEBhMC VVMxEzARBgNVBAgMCkNhbGlmb3JuaWExFjAUBgNVBAcMDVNhbiBGcmFuY2lzY28xDTALBgNVBAoM BE9rdGExFDASBgNVBAsMC1NTT1Byb3ZpZGVyMRAwDgYDVQQDDAdhc2EtZGV2MRwwGgYJKoZIhvcN AQkBFg1pbmZvQG9rdGEuY29tMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAqlQF++Ai iKrOb5MVwN8YEgFCbOdLSO44hcJq2BYZYRd1oq1XVnz7fVC49YgPXRafpXJx4v8jWyRQug2Sv4nE MvsbVzrV9N09/RHQ1MVa4QlTUEAhR0nSzs897k2e6zObf/zx5ugE+GLx03+chYFVv1ICup0e0pRN S6OWHYFzZnLTlCEgAbayHkbA82EViqgWD53BNQLvsS06WztF4pGISyxZ2NpycV5ejmI3ZSr6+bKX cgNAWr7inNBUaOwJG52/NlBAKaMq56Bljsni6YmZ/9V2DbQgTHSn4mu+++4FdDtFxBe1ZPIDJpjg uXf9X183H7ZIkNOxkr+YlW02uzOpBQIDAQABMA0GCSqGSIb3DQEBCwUAA4IBAQBRX6NORxMS4cDW kG/PqlYcCjgwZA/8rd6dBkI+wJEzqrXmO1SSIQW6F48ahDVqT0nicDYSnTkplIbKmooKjm2kkuCI jLwDiLldpZZ/Hpdj9rGDLC2jS6m3dr6OQvoTDYPOXfrgMykc5VM+h9yx+iYbrilmmrhOwIPxxZDV UiRSB6Op716xk+9d0jlyrtFF77B3YlKgMThQG6rguXViSwmViywWx+UQD6F1OzES8hoL54hfriOn lIpzZeamtJCo/jcdeqYHi3ru+uHOBe91GFPtoDGCVuk7YvzlXKMdgyDx82+kRSnLWYMxaI2zleFY nXHhoQk3K5iSdQT/gFgKJk89JaSnCMsKnmGg4Ew3yXuUdRPCmlzJngSWW1RZYH15Exk=nrIzAXSDsFwgvCm+ulbqfqZylzPxCBof6FYDcCEPdCQ=todd@okta.comhttps://dev.sudo.wtf:8443/v1/teams/asaurn:oasis:names:tc:SAML:2.0:ac:classes:PasswordProtectedTransportPhoebeYuphoebe.yu@okta.comphoebe.yu@okta.com` ) goxmldsig-1.1.1/xml_constants.go000066400000000000000000000047501411232664200167410ustar00rootroot00000000000000package 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, }