├── renovate.json
├── .gitignore
├── makefile
├── go.mod
├── testdata
├── rootxmlns.crt
├── root-level-signature.xml
├── doc.xml
├── windows-store-signature.xml
├── signature-generate-using-xmlseclib.xml
├── rsa.crt
├── nosignature.xml
├── nosignature2.xml
├── rootxmlns.xml
├── nosignature-custom-reference-id-attribute.xml
├── signature-with-inclusivenamespaces.xml
├── rsa.key.b64
├── bbauth-metadata.xml
├── saml-external-ns.xml
├── valid-saml.xml
├── invalid-signature-signature-value.xml
├── wsfed-metadata.xml
├── invalid-signature-changed-content.xml
└── invalid-signature-non-existing-reference.xml
├── .github
└── workflows
│ └── test.yml
├── tests
├── issue55_test.go
├── helpers.go
└── testdata
│ └── issue55.xml
├── LICENSE.md
├── CHANGELOG.md
├── examples
├── examples_validate.go
└── canonicalization_test.go
├── envelopedsignature.go
├── canonicalization.go
├── go.sum
├── signer.go
├── validator.go
├── README.md
├── exclusivecanonicalization.go
├── signedxml.go
└── signedxml_test.go
/renovate.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": [
3 | "config:recommended"
4 | ],
5 | "groupName": "all",
6 | "packageRules": [
7 | {
8 | "matchUpdateTypes": [
9 | "minor",
10 | "patch",
11 | "pin",
12 | "digest"
13 | ],
14 | "automerge": true
15 | }
16 | ]
17 | }
18 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # Compiled Object files, Static and Dynamic libs (Shared Objects)
2 | *.o
3 | *.a
4 | *.so
5 |
6 | # Folders
7 | _obj
8 | _test
9 |
10 | # Architecture specific extensions/prefixes
11 | *.[568vq]
12 | [568vq].out
13 |
14 | *.cgo1.go
15 | *.cgo2.c
16 | _cgo_defun.c
17 | _cgo_gotypes.go
18 | _cgo_export.*
19 |
20 | _testmain.go
21 |
22 | *.exe
23 | *.test
24 | *.prof
25 |
26 | /bin/
27 | /gitleaks.tar.gz
28 | /lint-project.sh
29 | /coverage.txt
--------------------------------------------------------------------------------
/makefile:
--------------------------------------------------------------------------------
1 | .PHONY: check
2 | check:
3 | ifeq ($(OS),Windows_NT)
4 | @echo "Skipping checks on Windows, currently unsupported."
5 | else
6 | @wget -O lint-project.sh https://raw.githubusercontent.com/moov-io/infra/master/go/lint-project.sh
7 | @chmod +x ./lint-project.sh
8 | COVER_THRESHOLD=70.0 ./lint-project.sh
9 | endif
10 |
11 | .PHONY: clean
12 | clean:
13 | @rm -rf ./bin/ ./tmp/ coverage.txt misspell* staticcheck lint-project.sh
14 |
15 | .PHONY: cover-test cover-web
16 | cover-test:
17 | go test -coverprofile=cover.out ./...
18 | cover-web:
19 | go tool cover -html=cover.out
20 |
--------------------------------------------------------------------------------
/go.mod:
--------------------------------------------------------------------------------
1 | module github.com/leifj/signedxml
2 |
3 | go 1.23.0
4 |
5 | toolchain go1.25.5
6 |
7 | require (
8 | github.com/beevik/etree v1.6.0
9 | github.com/russellhaering/goxmldsig v1.5.0
10 | github.com/smartystreets/goconvey v1.8.1
11 | github.com/stretchr/testify v1.11.1
12 | )
13 |
14 | require (
15 | github.com/davecgh/go-spew v1.1.1 // indirect
16 | github.com/gopherjs/gopherjs v1.17.2 // indirect
17 | github.com/jonboulle/clockwork v0.5.0 // indirect
18 | github.com/jtolds/gls v4.20.0+incompatible // indirect
19 | github.com/kr/text v0.2.0 // indirect
20 | github.com/pmezard/go-difflib v1.0.0 // indirect
21 | github.com/smarty/assertions v1.15.0 // indirect
22 | gopkg.in/yaml.v3 v3.0.1 // indirect
23 | )
24 |
--------------------------------------------------------------------------------
/testdata/rootxmlns.crt:
--------------------------------------------------------------------------------
1 | -----BEGIN CERTIFICATE-----
2 | MIIB/jCCAWegAwIBAgIBCjANBgkqhkiG9w0BAQQFADAkMSIwIAYDVQQDExlkZWFv
3 | YW0tZGV2MDIuanBsLm5hc2EuZ292MB4XDTE2MDYzMDA0NTQxNloXDTI2MDYyODA0
4 | NTQxNlowJDEiMCAGA1UEAxMZZGVhb2FtLWRldjAyLmpwbC5uYXNhLmdvdjCBnzAN
5 | BgkqhkiG9w0BAQEFAAOBjQAwgYkCgYEAht1N4lGdwUbl7YRyHwSCrnep6/e2I3+V
6 | eue0pSA/DGn8OuR/udM8UCja5utqlqJdq200ox4b4Mpz0Jg9kMckALtKe+1DgeES
7 | EIx9FpeuBdHlitYQNSbEr30HIG2nmeTOy4Vi5unBO54um3tNazcUTMA0/LJ6KQL8
8 | LeZSlB/IxwUCAwEAAaNAMD4wDAYDVR0TAQH/BAIwADAPBgNVHQ8BAf8EBQMDB9gA
9 | MB0GA1UdDgQWBBRYo1YjfrNonauLzj6/AsueWFGSszANBgkqhkiG9w0BAQQFAAOB
10 | gQACq7GHK/Zsg0+qC0WWa2ZjmOXE6Dqk/xuooG49QT7ihABs7k9U27Fw3xKF6MkC
11 | 7pca1FwT82eZK1N3XKKpZe7Flu1fMKt2o/XSiBkDjWwUcChVnwGsUBe8hJFwFqg7
12 | olNJn1kaVBJUqZIiXF9kS0d+1H55rStOd0CNXAzp9utr2A==
13 | -----END CERTIFICATE-----
14 |
--------------------------------------------------------------------------------
/.github/workflows/test.yml:
--------------------------------------------------------------------------------
1 | name: Go
2 |
3 | on:
4 | push:
5 | branches: [ master ]
6 | pull_request:
7 | branches: [ master ]
8 |
9 | jobs:
10 | build:
11 | name: Go Build (CGO)
12 | runs-on: ${{ matrix.os }}
13 | strategy:
14 | matrix:
15 | os: [ubuntu-latest, macos-latest, windows-latest]
16 | steps:
17 | - name: Set up Go 1.x
18 | uses: actions/setup-go@v6
19 | with:
20 | go-version: stable
21 | id: go
22 |
23 | - name: Check out code into the Go module directory
24 | uses: actions/checkout@v6
25 |
26 | - name: Test (Unix)
27 | if: runner.os != 'Windows'
28 | run: |
29 | make check
30 |
31 | - name: Test (Windows)
32 | if: runner.os == 'Windows'
33 | run: |
34 | go vet ./...
35 | go test -v ./...
36 |
--------------------------------------------------------------------------------
/tests/issue55_test.go:
--------------------------------------------------------------------------------
1 | package tests
2 |
3 | import (
4 | "os"
5 | "path/filepath"
6 | "testing"
7 |
8 | "github.com/leifj/signedxml"
9 |
10 | "github.com/stretchr/testify/require"
11 | )
12 |
13 | func TestIssue55(t *testing.T) {
14 | xml, err := os.ReadFile(filepath.Join("testdata", "issue55.xml"))
15 | require.NoError(t, err)
16 |
17 | signer, err := signedxml.NewSigner(string(xml))
18 | require.NoError(t, err)
19 |
20 | // Sign
21 | key := PrivateKey(t)
22 | xmlStr, err := signer.Sign(key)
23 | require.NoError(t, err)
24 |
25 | // Validate
26 | validator, err := signedxml.NewValidator(xmlStr)
27 | require.NoError(t, err)
28 |
29 | cert := TestCertificate(t)
30 | validator.Certificates = append(validator.Certificates, *cert)
31 |
32 | refs, err := validator.ValidateReferences()
33 | require.Contains(t, err.Error(), "does not match the expected digestvalue of")
34 | require.Len(t, refs, 0)
35 | }
36 |
--------------------------------------------------------------------------------
/tests/helpers.go:
--------------------------------------------------------------------------------
1 | package tests
2 |
3 | import (
4 | "crypto/rsa"
5 | "crypto/x509"
6 | "encoding/base64"
7 | "encoding/pem"
8 | "os"
9 | "path/filepath"
10 | "testing"
11 |
12 | "github.com/stretchr/testify/require"
13 | )
14 |
15 | func TestCertificate(t *testing.T) *x509.Certificate {
16 | pemBytes, err := os.ReadFile(filepath.Join("..", "testdata", "rsa.crt"))
17 | require.NoError(t, err)
18 |
19 | pemBlock, _ := pem.Decode(pemBytes)
20 |
21 | cert, err := x509.ParseCertificate(pemBlock.Bytes)
22 | require.NoError(t, err)
23 |
24 | return cert
25 | }
26 |
27 | func PrivateKey(t *testing.T) *rsa.PrivateKey {
28 | b64Bytes, err := os.ReadFile(filepath.Join("..", "testdata", "rsa.key.b64"))
29 | require.NoError(t, err)
30 |
31 | pemString, err := base64.StdEncoding.DecodeString(string(b64Bytes))
32 | require.NoError(t, err)
33 |
34 | pemBlock, _ := pem.Decode([]byte(pemString))
35 |
36 | key, err := x509.ParsePKCS1PrivateKey(pemBlock.Bytes)
37 | require.NoError(t, err)
38 |
39 | return key
40 | }
41 |
--------------------------------------------------------------------------------
/testdata/root-level-signature.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 | testtest
18 |
19 |
20 |
23 |
24 |
--------------------------------------------------------------------------------
/testdata/doc.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | Bruce
5 | Schneier
6 |
7 | Applied Cryptography
8 |
9 |
10 | XMLSec
11 | http://www.aleksey.com/xmlsec/
12 |
13 |
14 |
15 |
17 |
19 |
20 |
21 |
23 |
24 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
--------------------------------------------------------------------------------
/LICENSE.md:
--------------------------------------------------------------------------------
1 | The MIT License (MIT)
2 |
3 | Copyright (c) 2015 Matt Smith
4 | Copyright (c) 2023 Moov.io
5 |
6 | Permission is hereby granted, free of charge, to any person obtaining a copy
7 | of this software and associated documentation files (the "Software"), to deal
8 | in the Software without restriction, including without limitation the rights
9 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10 | copies of the Software, and to permit persons to whom the Software is
11 | furnished to do so, subject to the following conditions:
12 |
13 | The above copyright notice and this permission notice shall be included in all
14 | copies or substantial portions of the Software.
15 |
16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
22 | SOFTWARE.
23 |
--------------------------------------------------------------------------------
/testdata/windows-store-signature.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 | cdiU06eD8X/w1aGCHeaGCG9w/kWZ8I099rw4mmPpvdU=
14 |
15 |
16 | SjRIxS/2r2P6ZdgaR9bwUSa6ZItYYFpKLJZrnAa3zkMylbiWjh9oZGGng2p6/gtBHC2dSTZlLbqnysJjl7mQp/A3wKaIkzjyRXv3kxoVaSV0pkqiPt04cIfFTP0JZkE5QD/vYxiWjeyGp1dThEM2RV811sRWvmEs/hHhVxb32e8xCLtpALYx3a9lW51zRJJN0eNdPAvNoiCJlnogAoTToUQLHs72I1dECnSbeNPXiG7klpy5boKKMCZfnVXXkneWvVFtAA1h2sB7ll40LEHO4oYN6VzD+uKd76QOgGmsu9iGVyRvvmMtahvtL1/pxoxsTRedhKq6zrzCfT8qfh3C1w==
17 |
18 |
--------------------------------------------------------------------------------
/CHANGELOG.md:
--------------------------------------------------------------------------------
1 | ## v1.2.3 (Released 2025-03-20)
2 |
3 | IMPROVEMENTS
4 |
5 | - fix: verify type casting
6 |
7 | BUILD
8 |
9 | - chore(deps): update dependency go to v1.24.1 (#72)
10 | - fix(deps): update module github.com/beevik/etree to v1.5.0 (#68)
11 |
12 | ## v1.2.2 (Released 2024-06-10)
13 |
14 | IMPROVEMENTS
15 |
16 | - fix: allow no Transforms when signing
17 | - fix: removed XML declaration before signature operations
18 | - examples: run 3.6 in tests
19 |
20 | BUILD
21 |
22 | - fix(deps): update module github.com/beevik/etree to v1.4.0
23 |
24 | ## v1.2.1 (Released 2024-03-20)
25 |
26 | IMPROVEMENTS
27 |
28 | - fix: handle adjacent comments during canonicalization
29 | - feat: support http://www.w3.org/TR/2001/REC-xml-c14n-20010315
30 |
31 | ## v1.2.0 (Released 2024-02-19)
32 |
33 | ADDITIONS
34 |
35 | - signer: add convenience method for creating a signer given an already built etree.Document
36 |
37 | IMPROVEMENTS
38 |
39 | - fix: support Signature element on the root level
40 |
41 | BUILD
42 |
43 | - build: use latest stable Go release
44 | - fix(deps): update module github.com/beevik/etree to v1.3.0
45 | - fix(deps): update module github.com/smartystreets/goconvey to v1.8.1
46 |
47 | ## v1.1.1 (Released 2023-06-08)
48 |
49 | IMPROVEMENTS
50 |
51 | - Preserve CDATA text when signing a document
52 | - Typo in TestEnvelopedSignatureProcess
53 |
54 | ## v1.1.0 (Released 2023-05-30)
55 |
56 | IMPROVEMENTS
57 |
58 | - feat: replace Validate() with ValidateReferences()
59 | - meta: use moov-io/infra Go linter script in CI
60 |
61 | ## v1.0.0 (Released 2023-04-21)
62 |
63 | This is the first tagged release of the `moov-io/signedxml` package. It was previously released `ma314smith/signedxml` but has been moved over to the Moov.io Github organization.
64 |
--------------------------------------------------------------------------------
/examples/examples_validate.go:
--------------------------------------------------------------------------------
1 | package examples
2 |
3 | import (
4 | "fmt"
5 | "io"
6 | "os"
7 |
8 | "github.com/leifj/signedxml"
9 | )
10 |
11 | func ExampleValidate() {
12 | testValidator()
13 | testExclCanon()
14 | }
15 |
16 | func testValidator() {
17 | xmlFile, err := os.Open("../testdata/valid-saml.xml")
18 | if err != nil {
19 | fmt.Println("Error opening file:", err)
20 | return
21 | }
22 | defer xmlFile.Close()
23 |
24 | xmlBytes, _ := io.ReadAll(xmlFile)
25 |
26 | validator, err := signedxml.NewValidator(string(xmlBytes))
27 | if err != nil {
28 | fmt.Printf("Validation Error: %s", err)
29 | } else {
30 | refs, err := validator.ValidateReferences()
31 | if err != nil {
32 | fmt.Printf("Validation Error: %s\n", err)
33 | }
34 | if len(refs) == 0 {
35 | fmt.Println("ERROR: No Validated References")
36 | } else {
37 | fmt.Println("Example Validation Succeeded")
38 | }
39 | }
40 | }
41 |
42 | func testExclCanon() {
43 | var input = `
44 |
45 |
46 |
47 |
48 |
49 | Hello, world!
50 |
51 |
52 |
53 |
54 |
55 | `
56 |
57 | var output = `
58 | Hello, world!
59 | `
60 |
61 | transform := signedxml.ExclusiveCanonicalization{WithComments: false}
62 | resultXML, err := transform.Process(input, "")
63 |
64 | if err != nil {
65 | fmt.Printf("Transformation Error: %s\n", err)
66 | } else {
67 | if resultXML == output {
68 | fmt.Println("Example Tranformation Succeeded")
69 | } else {
70 | fmt.Println("Transformation Error: The transformed output did not match the expected output.")
71 | }
72 | }
73 | }
74 |
--------------------------------------------------------------------------------
/testdata/signature-generate-using-xmlseclib.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | Bruce
6 | Schneier
7 |
8 | Applied Cryptography
9 |
10 |
11 | XMLSec
12 | http://www.aleksey.com/xmlsec/
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 | kXaaD5Gor8lfNc6eYV31AyF0ekT9H/YY28Oh665mooQ=
24 |
25 |
26 | Z81G62a0rLOLlgOQyJJN5amwIQ9Wu3vqoCc5CDp8wHz8xVHBGv6MOcaXtzkXOveQ
27 | KFRuFsxWtlNdxyUyfHzkwHk1BLfzUNbG1BNNjgktJ63leO5w3C4v64/3/HnIPEq1
28 | eE1uSMO0HDevDrgethWofa2ExxBG2CU8bY4pGzLx95uhBUxk5ilkR1P4tayJp1Va
29 | U0/roVBZTo8WH+ol15hj5FFV4rjTMPV2kPy9geNsMDZi+N30C/RNHB7n5MScnBC+
30 | OiCMRmJEcgJpA8/GXCX+y7WFKsVB9p6VO5euC8UUD9dvezvHhMDBwqncjvbnvjOk
31 | BD58sY6v8LpLIabJUmg3Yt6YjU8dToxv36QrIjsN/m1p5sCsWIMWCdW9MtsR0VGl
32 | MjkBCQUE70aCUzVbNrtJZaGY+YH9oDvHsLHKzTZusOvYLGCtKwaBcqVEuad/7Nvs
33 | e7JjYVl6HfyHWZPY6PDt0Q180f+U9QV2C1EdI+r2sbMd+HMft2Jp6Wii3mHZD6hy
34 | jFr54lztG0x9/X75jwBIaoMv8RtTl8lKfFpBiUc9UrpxKJBOOhMPWiYdwrrAzE1g
35 | NEHZvU3wbZiFWCAo+4RtCB++pWnoEJ16tuGHKlEIWTJRAnqm4ld1hX0zo7ZVkabD
36 | ixthaEIn8XmlaL92X5ZAI0P+yFC5kXSDKtLVnf6TgfE=
37 |
38 |
39 |
40 |
41 |
--------------------------------------------------------------------------------
/testdata/rsa.crt:
--------------------------------------------------------------------------------
1 | -----BEGIN CERTIFICATE-----
2 | MIIFtTCCA52gAwIBAgIJAMXYnQaOVzIEMA0GCSqGSIb3DQEBBQUAMEUxCzAJBgNV
3 | BAYTAkFVMRMwEQYDVQQIEwpTb21lLVN0YXRlMSEwHwYDVQQKExhJbnRlcm5ldCBX
4 | aWRnaXRzIFB0eSBMdGQwHhcNMTYxMjEzMjIxNDU4WhcNMTcxMjEzMjIxNDU4WjBF
5 | MQswCQYDVQQGEwJBVTETMBEGA1UECBMKU29tZS1TdGF0ZTEhMB8GA1UEChMYSW50
6 | ZXJuZXQgV2lkZ2l0cyBQdHkgTHRkMIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIIC
7 | CgKCAgEAq++5R0F9+qrS7NP3Q0UmYWlPNac99AgiS0WWN3TW5ROMfduFTMwaN42g
8 | 1qfwv6Dj1G7SqyZFvKtF6C9mO/UZi27WVjlzymdHJ8whKoTqjuEKA+sAgOEp4i7Q
9 | q75ToRt8qu2GhKmZBGZ1cqWN4SRNsSCIfJJtyQCgmRvpESq0sv4yq4dbhVmiXtIL
10 | renkU1i/A6rp6tRGNjOeNVoamGn8NiWurrOJrkYPUJ8Td6lJ0CGWF50aewqcw6KU
11 | krQ6tHPi2TKfWHlQPCmUfY55kL8SqxggWqztrnnTqZBRW6PRYlCeF9R/MZlATKcL
12 | e4BO/MSWKIlU0Ry5oEJuhjz4xIdv64PdYQtEiKCsNbXyxgdyx+pMoG4cn54BnTDW
13 | L4erEMWy+DnNRi5qNAFr6XF8ggEt1fNqsrYJM0Iw8fEzx5r313F7EXBngADhoD2l
14 | vrc24zmOZZPCpD+MF7UQh3Gf8eHxkG549RJXDYrrOga5yQFO8At9MVzlpkdmolhr
15 | YmaxyV2LPUNIrV8JUfdeo/QeEHitPnoX5KdSPF8d5TejH3qI6y65YatZk+N3usSG
16 | qnR413ONKRNKDJWAwUZr1JmLc2hJv/kksYxaBtc1GLJjNiakDXZf62Cugs/g7aG8
17 | mxzli7fNTTUI0HbYh1WOsMvt5aNy7k+5w0BH5bgh+XzjdNnkMccCAwEAAaOBpzCB
18 | pDAdBgNVHQ4EFgQU3+VkpxukXURW1YIj5FnoNA4cB84wdQYDVR0jBG4wbIAU3+Vk
19 | pxukXURW1YIj5FnoNA4cB86hSaRHMEUxCzAJBgNVBAYTAkFVMRMwEQYDVQQIEwpT
20 | b21lLVN0YXRlMSEwHwYDVQQKExhJbnRlcm5ldCBXaWRnaXRzIFB0eSBMdGSCCQDF
21 | 2J0GjlcyBDAMBgNVHRMEBTADAQH/MA0GCSqGSIb3DQEBBQUAA4ICAQAVm/JxGP+a
22 | 2ZvlfTggY/CsS7BM24gIl9VMRBJPWYNlyUEVc2LblhTPrRsQYvELiXt7dr7+JB+5
23 | rhrGHdJXQYGmCin2laIFaPGHi5TJttBJ6nV+zKAyvjho5qru9+b8IOYajp9c6uLZ
24 | 8tFdc8DNC4KOQoL394wIzNNHzSs6W3YMoh4N4v0dQK6HPqK0Vs7UhCjDlfNGewcB
25 | sWBSw7bxVwdOV6wQbEFCddfLL5rzr38751L0/lGCLF7ff0aBSqGlVVdJzJjg6mA0
26 | 7sv7ZP5Vn0AbrKfA/Blo0Bh4LubR/t6mtPsicMbCBoPFA23ORX1aOspS1j1QfYU/
27 | +YvMphqmrvmqiTusv2z02/fTz7kZWb9vOb0RA56u3La9HMETeLhikoWDDWWWEC8a
28 | O9bMqY6rGXcBlszSb9oaWUv5wLB2CEBzybK4+fS2GUSkp9W00N7wSXVoVcu9PIcp
29 | Rws81AHDqyt37UbbKpDB/yTQcy98XfpyP85v36Din54iMOqda8RtLC0IQVbShZzf
30 | J5K5TsL4V3KJ21st7jDgykIoZ/RBjqzgPIbi2WNZWq64kJ3DQ+/MsSAUobvruhVc
31 | MT3DfEqU44tl343qIpIDwh6wZQMeW7mRPyGGQwK9viItnW47T4Q3vwkAMc696R5A
32 | Ud1dWMp2u3mcinuuG82ShqQM67fXdmEGiQ==
33 | -----END CERTIFICATE-----
34 |
--------------------------------------------------------------------------------
/envelopedsignature.go:
--------------------------------------------------------------------------------
1 | package signedxml
2 |
3 | import (
4 | "errors"
5 | "strings"
6 |
7 | "github.com/beevik/etree"
8 | )
9 |
10 | // EnvelopedSignature implements the CanonicalizationAlgorithm
11 | // interface and is used for processing the
12 | // http://www.w3.org/2000/09/xmldsig#enveloped-signature transform
13 | // algorithm
14 | type EnvelopedSignature struct{}
15 |
16 | // see CanonicalizationAlgorithm.ProcessElement
17 | func (e EnvelopedSignature) ProcessElement(inputXML *etree.Element, transformXML string) (outputXML string, err error) {
18 | transformedXML, err := e.processElement(inputXML.Copy(), transformXML)
19 | if err != nil {
20 | return "", err
21 | }
22 |
23 | doc := etree.NewDocument()
24 | doc.SetRoot(transformedXML)
25 | docString, err := doc.WriteToString()
26 | if err != nil {
27 | return "", err
28 | }
29 | //logger.Println(docString)
30 | return docString, nil
31 | }
32 |
33 | // see CanonicalizationAlgorithm.ProcessDocument
34 | func (e EnvelopedSignature) ProcessDocument(doc *etree.Document,
35 | transformXML string) (outputXML string, err error) {
36 |
37 | transformedRoot, err := e.processElement(doc.Root().Copy(), transformXML)
38 | if err != nil {
39 | return "", err
40 | }
41 | doc.SetRoot(transformedRoot)
42 | docString, err := doc.WriteToString()
43 | if err != nil {
44 | return "", err
45 | }
46 | //logger.Println(docString)
47 | return docString, nil
48 | }
49 |
50 | // see CanonicalizationAlgorithm.Process
51 | func (e EnvelopedSignature) Process(inputXML string, transformXML string) (outputXML string, err error) {
52 | doc := etree.NewDocument()
53 | err = doc.ReadFromString(inputXML)
54 | if err != nil {
55 | return "", err
56 | }
57 | return e.ProcessDocument(doc, transformXML)
58 | }
59 |
60 | func (e EnvelopedSignature) processElement(inputXML *etree.Element, transformXML string) (outputXML *etree.Element, err error) {
61 | sig := inputXML.FindElement("//Signature")
62 | if sig == nil {
63 | // TODO(adam): Why can't ./Signature (or /Signature, or //Signature) find the root Signature element?
64 | if strings.EqualFold(inputXML.Tag, "Signature") {
65 | sig = inputXML
66 | }
67 | }
68 | if sig == nil {
69 | return nil, errors.New("signedxml: unable to find Signature node")
70 | }
71 |
72 | sigParent := sig.Parent()
73 | if sigParent != nil {
74 | elem := sigParent.RemoveChild(sig)
75 | if elem == nil {
76 | return nil, errors.New("signedxml: unable to remove Signature element")
77 | }
78 | }
79 |
80 | return inputXML, nil
81 | }
82 |
--------------------------------------------------------------------------------
/tests/testdata/issue55.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | Hello World!
4 |
5 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 | HIDDEN
27 |
28 |
29 |
31 |
32 |
33 | 2024-06-04T16:13:09.320+07:00
34 |
35 |
36 |
37 |
38 | kiffFUcHZhaLu0OkrPZ1Ui99V784vgh4zJrJNNn82+XWUfDzz24SKy4GMId/hxDFxiQaak0AdJRWHPtLUgutIA==
39 |
40 |
41 |
42 | HIDDEN
43 | HIDDEN
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
--------------------------------------------------------------------------------
/testdata/nosignature.xml:
--------------------------------------------------------------------------------
1 |
2 | Test
3 |
4 |
5 |
6 | Test
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 | asdf
17 |
18 |
19 | asdf
20 |
21 |
22 | Test
23 |
24 |
25 |
26 |
27 |
28 | Test
29 |
30 |
31 |
32 |
33 | urn:oasis:names:tc:SAML:2.0:ac:classes:unspecified
34 |
35 |
36 |
37 |
--------------------------------------------------------------------------------
/testdata/nosignature2.xml:
--------------------------------------------------------------------------------
1 |
2 | Test
3 |
4 |
5 |
6 | Test
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 | asdf
17 |
18 |
19 | asdf
20 |
21 |
22 | Test
23 |
24 |
25 |
26 |
27 |
28 | Test
29 |
30 |
31 |
32 |
33 | urn:oasis:names:tc:SAML:2.0:ac:classes:unspecified
34 |
35 |
36 |
37 |
--------------------------------------------------------------------------------
/testdata/rootxmlns.xml:
--------------------------------------------------------------------------------
1 | https://deaoam-dev02.jpl.nasa.gov:14101/oam/fedhttps://deaoam-dev02.jpl.nasa.gov:14101/oam/fedz1HD/59hv6UOd5+jeG+ihaFWLgI=I99oG5kiOfIgbXYa21z/TOmzftTkFnXe9ObhBNSKit9kAhT93apYROqqXv4Ax96P144Ld7ERX1hgJsytK8LC2874Pk7QrSNm4zvW3x0D4GR4lM06CvJK/EhIur3TrCUJDPigvyP7TJitheCyBejwt0x0lqNP/OzR3tMbAIMRoho=pkieuJSAuthurn:oasis:names:tc:SAML:2.0:ac:classes:PasswordProtectedTransport
--------------------------------------------------------------------------------
/testdata/nosignature-custom-reference-id-attribute.xml:
--------------------------------------------------------------------------------
1 |
2 | Test
3 |
4 |
5 |
6 | Test
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 | asdf
17 |
18 |
19 | asdf
20 |
21 |
22 | Test
23 |
24 |
25 |
26 |
27 |
28 | Test
29 |
30 |
31 |
32 |
33 | urn:oasis:names:tc:SAML:2.0:ac:classes:unspecified
34 |
35 |
36 |
37 |
--------------------------------------------------------------------------------
/canonicalization.go:
--------------------------------------------------------------------------------
1 | package signedxml
2 |
3 | import (
4 | "github.com/beevik/etree"
5 | dsig "github.com/russellhaering/goxmldsig"
6 | )
7 |
8 | type c14N10RecCanonicalizer struct {
9 | WithComments bool
10 | }
11 |
12 | func (c *c14N10RecCanonicalizer) ProcessElement(inputXML *etree.Element, transformXML string) (outputXML string, err error) {
13 | transformedXML, err := c.processElement(inputXML, transformXML)
14 | if err != nil {
15 | return "", err
16 | }
17 | return transformedXML, nil
18 | }
19 |
20 | func (c *c14N10RecCanonicalizer) ProcessDocument(doc *etree.Document, transformXML string) (outputXML string, err error) {
21 |
22 | transformedXML, err := c.processElement(doc.Root(), transformXML)
23 | if err != nil {
24 | return "", err
25 | }
26 | return transformedXML, nil
27 | }
28 |
29 | func (c c14N10RecCanonicalizer) Process(inputXML string, transformXML string) (outputXML string, err error) {
30 | doc := etree.NewDocument()
31 | err = doc.ReadFromString(inputXML)
32 | if err != nil {
33 | return "", err
34 | }
35 | return c.ProcessDocument(doc, transformXML)
36 | }
37 |
38 | func (c *c14N10RecCanonicalizer) processElement(inputXML *etree.Element, transformXML string) (outputXML string, err error) {
39 | var canon dsig.Canonicalizer
40 | if c.WithComments {
41 | canon = dsig.MakeC14N10WithCommentsCanonicalizer()
42 | } else {
43 | canon = dsig.MakeC14N10RecCanonicalizer()
44 | }
45 |
46 | out, err := canon.Canonicalize(inputXML)
47 | if err != nil {
48 | return "", err
49 | }
50 | return string(out), nil
51 | }
52 |
53 | type c14N11Canonicalizer struct {
54 | WithComments bool
55 | }
56 |
57 | func (c *c14N11Canonicalizer) ProcessElement(inputXML *etree.Element, transformXML string) (outputXML string, err error) {
58 | transformedXML, err := c.processElement(inputXML, transformXML)
59 | if err != nil {
60 | return "", err
61 | }
62 | return transformedXML, nil
63 | }
64 |
65 | func (c *c14N11Canonicalizer) ProcessDocument(doc *etree.Document, transformXML string) (outputXML string, err error) {
66 |
67 | transformedXML, err := c.processElement(doc.Root(), transformXML)
68 | if err != nil {
69 | return "", err
70 | }
71 | return transformedXML, nil
72 | }
73 |
74 | func (c c14N11Canonicalizer) Process(inputXML string, transformXML string) (outputXML string, err error) {
75 | doc := etree.NewDocument()
76 | err = doc.ReadFromString(inputXML)
77 | if err != nil {
78 | return "", err
79 | }
80 | return c.ProcessDocument(doc, transformXML)
81 | }
82 |
83 | func (c *c14N11Canonicalizer) processElement(inputXML *etree.Element, transformXML string) (outputXML string, err error) {
84 | var canon dsig.Canonicalizer
85 | if c.WithComments {
86 | canon = dsig.MakeC14N11WithCommentsCanonicalizer()
87 | } else {
88 | canon = dsig.MakeC14N11Canonicalizer()
89 | }
90 |
91 | out, err := canon.Canonicalize(inputXML)
92 | if err != nil {
93 | return "", err
94 | }
95 | return string(out), nil
96 | }
97 |
--------------------------------------------------------------------------------
/go.sum:
--------------------------------------------------------------------------------
1 | github.com/beevik/etree v1.5.1 h1:TC3zyxYp+81wAmbsi8SWUpZCurbxa6S8RITYRSkNRwo=
2 | github.com/beevik/etree v1.5.1/go.mod h1:gPNJNaBGVZ9AwsidazFZyygnd+0pAU38N4D+WemwKNs=
3 | github.com/beevik/etree v1.6.0 h1:u8Kwy8pp9D9XeITj2Z0XtA5qqZEmtJtuXZRQi+j03eE=
4 | github.com/beevik/etree v1.6.0/go.mod h1:bh4zJxiIr62SOf9pRzN7UUYaEDa9HEKafK25+sLc0Gc=
5 | github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
6 | github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
7 | github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
8 | github.com/gopherjs/gopherjs v1.17.2 h1:fQnZVsXk8uxXIStYb0N4bGk7jeyTalG/wsZjQ25dO0g=
9 | github.com/gopherjs/gopherjs v1.17.2/go.mod h1:pRRIvn/QzFLrKfvEz3qUuEhtE/zLCWfreZ6J5gM2i+k=
10 | github.com/jonboulle/clockwork v0.5.0 h1:Hyh9A8u51kptdkR+cqRpT1EebBwTn1oK9YfGYbdFz6I=
11 | github.com/jonboulle/clockwork v0.5.0/go.mod h1:3mZlmanh0g2NDKO5TWZVJAfofYk64M7XN3SzBPjZF60=
12 | github.com/jtolds/gls v4.20.0+incompatible h1:xdiiI2gbIgH/gLH7ADydsJ1uDOEzR8yvV7C0MuV77Wo=
13 | github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU=
14 | github.com/kr/pretty v0.3.0 h1:WgNl7dwNpEZ6jJ9k1snq4pZsg7DOEN8hP9Xw0Tsjwk0=
15 | github.com/kr/pretty v0.3.0/go.mod h1:640gp4NfQd8pI5XOwp5fnNeVWj67G7CFk/SaSQn7NBk=
16 | github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
17 | github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
18 | github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
19 | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
20 | github.com/rogpeppe/go-internal v1.8.0 h1:FCbCCtXNOY3UtUuHUYaghJg4y7Fd14rXifAYUAtL9R8=
21 | github.com/rogpeppe/go-internal v1.8.0/go.mod h1:WmiCO8CzOY8rg0OYDC4/i/2WRWAB6poM+XZ2dLUbcbE=
22 | github.com/russellhaering/goxmldsig v1.5.0 h1:AU2UkkYIUOTyZRbe08XMThaOCelArgvNfYapcmSjBNw=
23 | github.com/russellhaering/goxmldsig v1.5.0/go.mod h1:x98CjQNFJcWfMxeOrMnMKg70lvDP6tE0nTaeUnjXDmk=
24 | github.com/smarty/assertions v1.15.0 h1:cR//PqUBUiQRakZWqBiFFQ9wb8emQGDb0HeGdqGByCY=
25 | github.com/smarty/assertions v1.15.0/go.mod h1:yABtdzeQs6l1brC900WlRNwj6ZR55d7B+E8C6HtKdec=
26 | github.com/smartystreets/goconvey v1.8.1 h1:qGjIddxOk4grTu9JPOU31tVfq3cNdBlNa5sSznIX1xY=
27 | github.com/smartystreets/goconvey v1.8.1/go.mod h1:+/u4qLyY6x1jReYOp7GOM2FSt8aP9CzCZL03bI28W60=
28 | github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA=
29 | github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
30 | github.com/stretchr/testify v1.11.0 h1:ib4sjIrwZKxE5u/Japgo/7SJV3PvgjGiRNAvTVGqQl8=
31 | github.com/stretchr/testify v1.11.0/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U=
32 | github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U=
33 | github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U=
34 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
35 | gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
36 | gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
37 | gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
38 | gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
39 |
--------------------------------------------------------------------------------
/testdata/signature-with-inclusivenamespaces.xml:
--------------------------------------------------------------------------------
1 | http://www.okta.com/k7xkhq0jUHUPQAXVMUAN4G+uveKmtiB1EkY5BAt+8lmQwjI=Q80N6FUr5/YPtEzRlRdMoPu+bL0MssDxNUY+yxykzbmxsI0joEo/SmmSgZrDYQKTllZk/KfzBMPFV9yBH4+mEzCU5E3xuCs99jZzafcw3K8mIMTJy1YHxjc359d27R5s50i9w5PHsusRov0MjQIoJ2w48Gy4EnYaViqBR3UVEqE=MIICnTCCAgagAwIBAgIGAUBGHxqUMA0GCSqGSIb3DQEBBQUAMIGRMQswCQYDVQQGEwJVUzETMBEG
2 | A1UECAwKQ2FsaWZvcm5pYTEWMBQGA1UEBwwNU2FuIEZyYW5jaXNjbzENMAsGA1UECgwET2t0YTEU
3 | MBIGA1UECwwLU1NPUHJvdmlkZXIxEjAQBgNVBAMMCWtsdWdsYWJzMjEcMBoGCSqGSIb3DQEJARYN
4 | aW5mb0Bva3RhLmNvbTAeFw0xMzA4MDMyMTM4MzhaFw00MzA4MDMyMTM5MzhaMIGRMQswCQYDVQQG
5 | EwJVUzETMBEGA1UECAwKQ2FsaWZvcm5pYTEWMBQGA1UEBwwNU2FuIEZyYW5jaXNjbzENMAsGA1UE
6 | CgwET2t0YTEUMBIGA1UECwwLU1NPUHJvdmlkZXIxEjAQBgNVBAMMCWtsdWdsYWJzMjEcMBoGCSqG
7 | SIb3DQEJARYNaW5mb0Bva3RhLmNvbTCBnzANBgkqhkiG9w0BAQEFAAOBjQAwgYkCgYEAsCB9lJTH
8 | qB7vdM5jeOH84cW8u7IHYv4/OAPYF0fBYe9wJy19CgyM2OgiASuAcItnH4WhB+io2ZPwb/Xwl7Uu
9 | 4XmUE0l+mkCNuDYp5fXTZxwv5G6HvkAxXZio0Rk9T0VETCroxgpS5LxQ/o/owjR39S7xzRnj6ddX
10 | 3Mq2yGjKyBcCAwEAATANBgkqhkiG9w0BAQUFAAOBgQAB1qGNqSNLLWq+RPcP+wOaWtYpJOJ8/MbZ
11 | EWWm9/KKHKXM6J/zgUUIXZi3czMeO+Y+X14PR8lGXoAHf5b/JavG9FmFvRn4fGa45VTVo2GfMN6K
12 | aIKF0obeCbYi/QUf8B+Xi1tSIJm1VCKRE7nnliQ/TzGaNulgWeyTbVkG0/X8LQ==admin@kluglabs.comhttps://auth0145.auth0.comurn:oasis:names:tc:SAML:2.0:ac:classes:PasswordProtectedTransportAdmin
--------------------------------------------------------------------------------
/testdata/rsa.key.b64:
--------------------------------------------------------------------------------
1 | LS0tLS1CRUdJTiBSU0EgUFJJVkFURSBLRVktLS0tLQpNSUlKS0FJQkFBS0NBZ0VBcSsrNVIwRjkrcXJTN05QM1EwVW1ZV2xQTmFjOTlBZ2lTMFdXTjNUVzVST01mZHVGClRNd2FONDJnMXFmd3Y2RGoxRzdTcXlaRnZLdEY2QzltTy9VWmkyN1dWamx6eW1kSEo4d2hLb1RxanVFS0Erc0EKZ09FcDRpN1FxNzVUb1J0OHF1MkdoS21aQkdaMWNxV040U1JOc1NDSWZKSnR5UUNnbVJ2cEVTcTBzdjR5cTRkYgpoVm1pWHRJTHJlbmtVMWkvQTZycDZ0UkdOak9lTlZvYW1HbjhOaVd1cnJPSnJrWVBVSjhUZDZsSjBDR1dGNTBhCmV3cWN3NktVa3JRNnRIUGkyVEtmV0hsUVBDbVVmWTU1a0w4U3F4Z2dXcXp0cm5uVHFaQlJXNlBSWWxDZUY5Ui8KTVpsQVRLY0xlNEJPL01TV0tJbFUwUnk1b0VKdWhqejR4SWR2NjRQZFlRdEVpS0NzTmJYeXhnZHl4K3BNb0c0YwpuNTRCblREV0w0ZXJFTVd5K0RuTlJpNXFOQUZyNlhGOGdnRXQxZk5xc3JZSk0wSXc4ZkV6eDVyMzEzRjdFWEJuCmdBRGhvRDJsdnJjMjR6bU9aWlBDcEQrTUY3VVFoM0dmOGVIeGtHNTQ5UkpYRFlyck9nYTV5UUZPOEF0OU1WemwKcGtkbW9saHJZbWF4eVYyTFBVTklyVjhKVWZkZW8vUWVFSGl0UG5vWDVLZFNQRjhkNVRlakgzcUk2eTY1WWF0WgprK04zdXNTR3FuUjQxM09OS1JOS0RKV0F3VVpyMUptTGMyaEp2L2trc1l4YUJ0YzFHTEpqTmlha0RYWmY2MkN1CmdzL2c3YUc4bXh6bGk3Zk5UVFVJMEhiWWgxV09zTXZ0NWFOeTdrKzV3MEJINWJnaCtYempkTm5rTWNjQ0F3RUEKQVFLQ0FnQWRRVEhHUlFkNWNqMW5YSDJTN1o0bEx2VHlUUjJHZXlmU3BPbDFVQ1VNWHpJbGJBZVhWSmNWWXJ5KwpLVjhXUy9yWDkyOVMvYlVaOEE1NS9pNm4vd0UwcjB3NjZaV2hGOGVNcHZmeHN5SHg0NnAvbGluRXhBc0xXQ25iClB3d2R1bjZRMnM5cm5TdlRDdGZyQk81S0k1SXdsRGVFOHFsSmJSam1WRkJvd2xweXBqdmE0YklaMDNHRUxyc2MKQVZML04zZHcyVkRweXVxVGFGNy85dS9WaFJzTDM0ZUVaRG9GOUJmQkFRQWYrU2I5Q2VudTVLVlAwRE55T2lTTApmYTBMYlRobyttc1FjN3ZLTU16NlBCYkZJUzI4L09Mc2FzWWFUTkpONUtkakwxRitKL2R1V0pLM3pycVpFRWNzCjRWMFBOeWZSaCtSdG5EU2RaeEJGT1BTZkEraHY0OXhDbFc0NnlZc3RRWFF2YUx1UHNzNUFhY2cwU2x0eDFLTFcKTzQvc1pmS1Nqb1Ewdm16aVozU1RrYVhOYjcycUxKVUY4Sm03QXhHaEgwRVkyWjNDK0Z3ekpObnBsTTRNYlJEbgpJeHpxNVFZejVHSzZIWWI5cjZZMjRrdHJGb2xPNkVUNkRJaUdscWJyS1hhajZacFpKYjduN05Cckx3aXZ5aWpVClNSNEh4Zytib2ovdEdJaTRYZmZRcVYvVXowSXZrcWxqVE5yUEJ6dUJoQmw2WFBjRi91c3BjVVJ5a1VVb3dlNXAKTFVlbmNpeVJhYTNjSldWZklDVE1nbWFINGpvSDFiWlZsSjhxOW1BNFdlN0VWbGRLWHdLclRnblRBN2VNL2pwcQpPd21MUXpKRzVQL0Y1eVhjVVEwZmJiek1tY2V6b3RCZXBNWmRZU0p2RjRaeEthWFFjUUtDQVFFQTFmMHNoSVM3Cm90YkN3UFduZWpuWWVpOFpWODRLYThlTEZxbWYxYnlTMjBkMzlJN2d6a2pPQTFCczlETWthNElhdFhQYTJHdUsKNGo3V2FualFUT1F4Y3BqbDYySm1kd3FKK3pRazhTR3ZPcW5oWVByNzBQcnB4dFNYL2JDaW1IRmZUSWo5VklmMwpUVzh6L215N21hSlFIYTlKT2ZuMnFFMWI0My9uNEdLaFZVVFoycU9EMURoaGFwL3JDMUhQZXFKZXBWckIrQng5CllxU21ZMkMwMzdMMm40S2NjWklMRk53RmZaMTd0aFM5dnk4TTZYQU02bVpQeGkrZUJ5VExuQkNER2l4R3RQYUUKUjBkV3orU2dwVjRpN3BQcjgvNkticXFmRGJhNkJpWXBFeTZHdjNNcTU3SnpFSkZQSEppKzhySnRRTzd4TXlBZwprSkcwMG83RXkram5md0tDQVFFQXpiRUtSTUFkL2JoR1NqNDY3ZG4rN1FkWWU0TkV6dzVneklvR1RzWXp4QU0zClZRdERjK0hzc3l5Y3Naa3Y2TmdFdng1Nm51RmVWMWhKanhUMUZhM0xqU3V4Q2l3dnhKTDlUVkZ1dEhrZFJOMTQKYUZsRU1YbDdkM1F4TVJOTVpUOVRtZ0lZejhXQ3dhOS9yUWF6b3ArWExnKzE5U0tsZ244ZWZiaXB5NmdrZU1nRAppb3BvcEY5SFo4MVU4cHpGKzhydys5d0RxY1E1TmlGNy8wZzJ6V2NHY2RqNis0VmYyM0tpNktxSFk5bW1SeHkyCm5jM1Z6NUl0bW9BYlVSQVRiYStzcmlpZkhlV3daV0pYbkU5NEJtUEVnWHVNRWNXN21UMFdDZXg2UmtDRHpMelkKK3RDbVl6QythUUFJbk5pMm1TVUduUGZyOGJpSmY5RVlZWkRqd01hWnVRS0NBUUVBcys0WHVxM3BJRGs0dFF0Wgo4WG9tUGtiUUpuYUhhS3oybE8yNENmNXY5WldZbGJoMTZyNXBDM3hhd05NbjhvWUpjejJoYkl5dzBTQkpKbW5ECnJJUEwyREljVWRvQmttTDROcE5YNUxHUUorR2tNdW1YUjlkVEx1NmZocDg1ZUxrTGlzMDlSaEMxcnhTc1FXaW8KTGJ5L1pOWjQyaGtHZituY00rVGUwbHZja0ZnK1hvdFRVK1M4ZE8xV3M4UHNrNDlueWQ0UWIxRjFWTFpoZWZwYwozQ1FkSDI2eG9RVk44Qkk1UTdCaHoyNk5SZTZJQ3RrKzR2RjRib3FuZEJaS1prVzBGeFBYdUYzNFIvOENUZmhOCjQvN05uSklndXA3elE2NlA0dlljWVFOQlVuR3l0OVJjNVVtMVd0NzBDSFdmU1c4aVVwRVhtd2VPbWtFS0xHb0QKcS9tQWZRS0NBUUFGZ1VsL0VZMDc3eVNpZEF2cnRidm02QjFzSU5WRW5XUGwxWVBacytCS3pMTThhWExDdFRjNgpqR1VkTTdaZWl2bUZpL0ZJTTB2dEZvWFJlSVFlc21vU095c2YxSkxxdGV4bGwxcFpKaXlzWFNrTjBlWExWUzhDCndXOVhtdWFlaHhWYnQ5YW1LT2tZK2ZOMjFBa1d2eUt5cnVUMXpKbVZab1dRWTlMWGlvWi9YUXNMT0MwWXlSd0cKSVM2MjE0OU03OStIUGhveS92ZHhCWEJtSXE2a3pLOFZlUE11ckNFbXBHakx4VlNlVFlMdndFcjY2amxndHM3VgpwYndWYlZLM3lzM2FVMmYreXRqdnZiUU9rT25yY2R3ZWd3bjZwOG9mamN6N013TUt3a3RFdllFcEpWQnVzeTFGCmJpVE0zZGYxUFZKeC9RaXBqWURRbE80TUhtNmFDRGF4QW9JQkFBNDhPOTRwbzRYa2x2SlBNeUdhSE43RGs5SngKemVUMGx1R0c5dW5kUjhEVlI1QlJ5WnFadk9DdXNQQXZCWUFnYjhDVWVTMWlaOUp0YnE1bnZuWFRndW4vZzk1dAp3a2pLWFlSNHZNVnRGWGpJc1owQ3o5dnFMVlJISjNtMm5sWXUrenRpWlVJbFNUQyswWlpVM0xlb1dNaGk0c3VWCkR6TnF4ZWw1YTYyQmloVGlZRW5rN09ocUxSc0ZWeTM4ZHVSanJOOEtXcFVxREdKbmMxWjYrbm9nTWkweCtWT3MKR210QmM0M2xvVFdwSTRVc0RGR2lKSDF2UUlKQXhRbDJETmhucFBGQXo4Y2J3UFNjR1VxVWk2NGlUa0pNbzRVeAp2TjkyOXc4QTBaV21PVTNxVjR3WkhWcXRIN2FSRjhQR082VEJNWVFYT1Q4SkZCQkZUR0FxSURGRzZaND0KLS0tLS1FTkQgUlNBIFBSSVZBVEUgS0VZLS0tLS0K
2 |
--------------------------------------------------------------------------------
/testdata/bbauth-metadata.xml:
--------------------------------------------------------------------------------
1 | LPHoiAkLmA/TGIuVbgpwlFLXL+ymEBc7TS0fC9/PTQU=d2CXq9GEeDKvMxdpxtTRKQ8TGeSWhJOVPs8LMD0ObeE1t/YGiAm9keorMiki4laxbWqAuOmwHK3qNHogRFgkIYi3fnuFBzMrahXf0n3A5PRXXW1m768Z92GKV09pGuygKUXCtXzwq0seDi6PnzMCJFzFXGQWnum0paa8Oz+6425Sn0zO0fT3ttp3AXeXGyNXwYPYcX1iEMB7klUlyiz2hmn8ngCIbTkru7uIeyPmQ5WD4SS/qQaL4yb3FZibXoe/eRXrbkG1NAJCw9OWw0jsvWncE1rKFaqEMbz21fXSDhh3Ls+p9yVf+xbCrpkT0FMqjTHpNRvccMPZe/hDGrHV7Q==MIIDNzCCAh+gAwIBAgIQQVK+d/vLK4ZNMDk15HGUoTANBgkqhkiG9w0BAQ0FADAoMSYwJAYDVQQDEx1CbGFja2JhdWQgQXV0aGVudGljYXRpb24gMjAyMjAeFw0wMDAxMDEwNDAwMDBaFw0yMjAxMDEwNDAwMDBaMCgxJjAkBgNVBAMTHUJsYWNrYmF1ZCBBdXRoZW50aWNhdGlvbiAyMDIyMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEArgByjSPVvP4DLf/l7QRz7G7Dhkdns0QjWslnWejHlFIezfkJ4NGPp0+5CRCFYBqAb7DhqyK77Ek5xdzmwgYb1X6GD6UDltWvN5BBFAw69I6/K0WjguFUxk19T7xdc8vTCNAMi+6Ys49O3EBNnI2fiqDoBdMjUTud1F04QY3N2rZWkjMrHV+CnzhoUwqsO/ABWrDbkPzBXdOOIbsKH0k0IP8q2+35pe1y2nxtB9f1fCyCmbUH2HINMHahDmxxanTW5Jy14yD/HSRTFQF9JMTeglomWq5q9VPx0NjsEJR+B5IkRCTf75LoYrrr/fvQm3aummmYPdHauXCBrcm0moX4ywIDAQABo10wWzBZBgNVHQEEUjBQgBDCHOfardZfhltQSbLqsukZoSowKDEmMCQGA1UEAxMdQmxhY2tiYXVkIEF1dGhlbnRpY2F0aW9uIDIwMjKCEEFSvnf7yyuGTTA5NeRxlKEwDQYJKoZIhvcNAQENBQADggEBADrOhfRiynRKGD7EHohpPrltFScJ9+QErYMhEvteqh3C48T99uKgDY8wTqv+PI08QUSZuhmmF2d+W7aRBo3t8ZZepIXCwDaKo/oUp2h5Y9O3vyGDguq5ptgDTmPNYDCwWtdt0TtQYeLtCQTJVbYByWL0eT+KdzQOkAi48cPEOObSc9Biga7LTCcbCVPeJlYzmHDQUhzBt2jcy5BGvmZloI5SsoZvve6ug74qNq8IJMyzJzUp3kRuB0ruKIioSDi1lc783LDT3LSXyIbOGw/vHBEBY4Ax7FK8CqXJ2TsYqVsyo8QypqXDnveLcgK+PNEAhezhxC9hyV8j1I8pfF72ABE=MIIDNzCCAh+gAwIBAgIQQVK+d/vLK4ZNMDk15HGUoTANBgkqhkiG9w0BAQ0FADAoMSYwJAYDVQQDEx1CbGFja2JhdWQgQXV0aGVudGljYXRpb24gMjAyMjAeFw0wMDAxMDEwNDAwMDBaFw0yMjAxMDEwNDAwMDBaMCgxJjAkBgNVBAMTHUJsYWNrYmF1ZCBBdXRoZW50aWNhdGlvbiAyMDIyMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEArgByjSPVvP4DLf/l7QRz7G7Dhkdns0QjWslnWejHlFIezfkJ4NGPp0+5CRCFYBqAb7DhqyK77Ek5xdzmwgYb1X6GD6UDltWvN5BBFAw69I6/K0WjguFUxk19T7xdc8vTCNAMi+6Ys49O3EBNnI2fiqDoBdMjUTud1F04QY3N2rZWkjMrHV+CnzhoUwqsO/ABWrDbkPzBXdOOIbsKH0k0IP8q2+35pe1y2nxtB9f1fCyCmbUH2HINMHahDmxxanTW5Jy14yD/HSRTFQF9JMTeglomWq5q9VPx0NjsEJR+B5IkRCTf75LoYrrr/fvQm3aummmYPdHauXCBrcm0moX4ywIDAQABo10wWzBZBgNVHQEEUjBQgBDCHOfardZfhltQSbLqsukZoSowKDEmMCQGA1UEAxMdQmxhY2tiYXVkIEF1dGhlbnRpY2F0aW9uIDIwMjKCEEFSvnf7yyuGTTA5NeRxlKEwDQYJKoZIhvcNAQENBQADggEBADrOhfRiynRKGD7EHohpPrltFScJ9+QErYMhEvteqh3C48T99uKgDY8wTqv+PI08QUSZuhmmF2d+W7aRBo3t8ZZepIXCwDaKo/oUp2h5Y9O3vyGDguq5ptgDTmPNYDCwWtdt0TtQYeLtCQTJVbYByWL0eT+KdzQOkAi48cPEOObSc9Biga7LTCcbCVPeJlYzmHDQUhzBt2jcy5BGvmZloI5SsoZvve6ug74qNq8IJMyzJzUp3kRuB0ruKIioSDi1lc783LDT3LSXyIbOGw/vHBEBY4Ax7FK8CqXJ2TsYqVsyo8QypqXDnveLcgK+PNEAhezhxC9hyV8j1I8pfF72ABE=EmailThe users email addressUser IdentifierThe Id of the userFirst nameThe first name of the userLast nameThe last name of the userhttps://signin.blackbaud.com/WSFEDERATION/ACTIONhttps://signin.blackbaud.com/WSFEDERATION/ACTION
2 |
--------------------------------------------------------------------------------
/testdata/saml-external-ns.xml:
--------------------------------------------------------------------------------
1 | https://app.onelogin.com/saml/metadata/164679https://app.onelogin.com/saml/metadata/164679Gx0mTydMn1k6804jZBrdUrZmbV4=oHEPKtwoCbfq1QRm2pjx35zVMqAsti4nQU+3ws8EUJUXHmPG2EoX3HBkb7D2wN4m+ZFrdwARUpNJlhhOIz/eG4jES6ar0tvlNN3qE5cqcQhwZHyRARLnTlERqyZU9Qm729DnAGBeXCdMb736zi16onOIVPNA63LRTzUIxhyZqypDCf1wd6me/ur6UUgH11nYOu4JDYx0iWNkXc1Nad7vkF9oMPeO1QsMxuZSIVH4tvdJkue+qAnu2l+dFJb0LPfm+xmIC0FBo+VX1ECCWRoUZIxjotQfAM6yZpHIi5fNqPXkVyN9fYoUEa9CafqHlc4tAAdgAgGeOqA3jWeC8ZnOVA==MIIEHjCCAwagAwIBAgIBATANBgkqhkiG9w0BAQUFADBnMQswCQYDVQQGEwJVUzETMBEGA1UECAwKQ2FsaWZvcm5pYTEVMBMGA1UEBwwMU2FudGEgTW9uaWNhMREwDwYDVQQKDAhPbmVMb2dpbjEZMBcGA1UEAwwQYXBwLm9uZWxvZ2luLmNvbTAeFw0xMzA1MjcwODU1MTNaFw0xODA1MjcwODU1MTNaMGcxCzAJBgNVBAYTAlVTMRMwEQYDVQQIDApDYWxpZm9ybmlhMRUwEwYDVQQHDAxTYW50YSBNb25pY2ExETAPBgNVBAoMCE9uZUxvZ2luMRkwFwYDVQQDDBBhcHAub25lbG9naW4uY29tMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAoXoc7IFZQRv+SwJ15zjIl9touwY5e6b7H4vn3OtOUByjOKHUX8VX0TpbAV2ctZE2GSALx1AGuQAv6O4MVUH+qn/2IAiBY3a7zKN07UBsya7xFMQVHuGE6EiBAs9jpA9wjvYMPRkS5wYZcwjpTQSZK7zFPPtobG8K/1vDbm/tWZjNLmZmQePmXpwrQAuC0+NlzlmnjoQYB2xp2NaTUK9JnnmuB5qev3dpUwlYGSJpf+HUIoxuo8IpxAXOymq1d6tEEJgU1kR2sa7o1sSRFo31YeW/qYCP/gcLJZo3MRUDFe0g5MHeliFue9DsKYUsC6qwAD3gc+MI47buiD6Msu11cwIDAQABo4HUMIHRMAwGA1UdEwEB/wQCMAAwHQYDVR0OBBYEFAAJFJRIlpQQSFsuNdeq7FkTJIH4MIGRBgNVHSMEgYkwgYaAFAAJFJRIlpQQSFsuNdeq7FkTJIH4oWukaTBnMQswCQYDVQQGEwJVUzETMBEGA1UECAwKQ2FsaWZvcm5pYTEVMBMGA1UEBwwMU2FudGEgTW9uaWNhMREwDwYDVQQKDAhPbmVMb2dpbjEZMBcGA1UEAwwQYXBwLm9uZWxvZ2luLmNvbYIBATAOBgNVHQ8BAf8EBAMCBPAwDQYJKoZIhvcNAQEFBQADggEBAB9zN+g6N4sUBE61RaMUH2LSHWwOtfhL64i7pjHjvZa47/qcV/S0Yyd4IE44ho9i2N+AM79d34mThc30oK5aVxOKphKf+xM/cOyVaWIeqr+dCbkY/0OpLEwWOh9VSgOizRO3evLMurbtR892LbSK/Td3hG5jfwoHD23nHH87Dv/3KyZox9MkJdY2DXOHGGIcsqoIifaTyNZyhW6RgwEujQ6LjsaolP1YoeV85TZFKTLa1Ta7ZLUVUC2UJWqz+kRlsyGxf+E/ZmJ7hSq0ZBVHrVOyXjCcFn6X0/W5SrpOmN3fZYcj8Bp6vhB0cJk9qpjgWOP2RCuBdHZVawjCjIaE+bc=kartik.cds@gmail.comForNodeJSurn:oasis:names:tc:SAML:2.0:ac:classes:PasswordProtectedTransportkartik.cds@gmail.comKartikCDS
--------------------------------------------------------------------------------
/signer.go:
--------------------------------------------------------------------------------
1 | package signedxml
2 |
3 | import (
4 | "crypto"
5 | "crypto/rand"
6 | "crypto/rsa"
7 | "crypto/x509"
8 | "encoding/base64"
9 | "errors"
10 | "fmt"
11 |
12 | "github.com/beevik/etree"
13 | )
14 |
15 | var signingAlgorithms map[x509.SignatureAlgorithm]cryptoHash
16 |
17 | func init() {
18 | signingAlgorithms = map[x509.SignatureAlgorithm]cryptoHash{
19 | // MD2 not supported
20 | // x509.MD2WithRSA: cryptoHash{algorithm: "rsa", hash: crypto.MD2},
21 | x509.MD5WithRSA: {algorithm: "rsa", hash: crypto.MD5},
22 | x509.SHA1WithRSA: {algorithm: "rsa", hash: crypto.SHA1},
23 | x509.SHA256WithRSA: {algorithm: "rsa", hash: crypto.SHA256},
24 | x509.SHA384WithRSA: {algorithm: "rsa", hash: crypto.SHA384},
25 | x509.SHA512WithRSA: {algorithm: "rsa", hash: crypto.SHA512},
26 | // DSA not supported
27 | // x509.DSAWithSHA1: cryptoHash{algorithm: "dsa", hash: crypto.SHA1},
28 | // x509.DSAWithSHA256:cryptoHash{algorithm: "dsa", hash: crypto.SHA256},
29 | // Golang ECDSA support is lacking, can't seem to load private keys
30 | // x509.ECDSAWithSHA1: cryptoHash{algorithm: "ecdsa", hash: crypto.SHA1},
31 | // x509.ECDSAWithSHA256: cryptoHash{algorithm: "ecdsa", hash: crypto.SHA256},
32 | // x509.ECDSAWithSHA384: cryptoHash{algorithm: "ecdsa", hash: crypto.SHA384},
33 | // x509.ECDSAWithSHA512: cryptoHash{algorithm: "ecdsa", hash: crypto.SHA512},
34 | }
35 | }
36 |
37 | type cryptoHash struct {
38 | algorithm string
39 | hash crypto.Hash
40 | }
41 |
42 | // Signer provides options for signing an XML document
43 | type Signer struct {
44 | signatureData
45 | privateKey interface{}
46 | }
47 |
48 | // NewSigner returns a *Signer for the XML provided
49 | func NewSigner(xml string) (*Signer, error) {
50 | doc, err := parseXML(xml)
51 | if err != nil {
52 | return nil, err
53 | }
54 | return NewSignerFromDoc(doc)
55 | }
56 |
57 | // NewSignerFromDoc returns a *Signer for the Document provided
58 | func NewSignerFromDoc(doc *etree.Document) (*Signer, error) {
59 | s := &Signer{signatureData: signatureData{xml: doc}}
60 | return s, nil
61 | }
62 |
63 | // Sign populates the XML digest and signature based on the parameters present and privateKey given
64 | func (s *Signer) Sign(privateKey interface{}) (string, error) {
65 | s.privateKey = privateKey
66 |
67 | if s.signature == nil {
68 | if err := s.parseEnvelopedSignature(); err != nil {
69 | return "", err
70 | }
71 | }
72 | if err := s.parseSignedInfo(); err != nil {
73 | return "", err
74 | }
75 | if err := s.parseSigAlgorithm(); err != nil {
76 | return "", err
77 | }
78 | if err := s.parseCanonAlgorithm(); err != nil {
79 | return "", err
80 | }
81 | if err := s.setDigest(); err != nil {
82 | return "", err
83 | }
84 | if err := s.setSignature(); err != nil {
85 | return "", err
86 | }
87 |
88 | xml, err := s.xml.WriteToString()
89 | if err != nil {
90 | return "", err
91 | }
92 | return xml, nil
93 | }
94 |
95 | // SetReferenceIDAttribute set the referenceIDAttribute
96 | func (s *Signer) SetReferenceIDAttribute(refIDAttribute string) {
97 | s.signatureData.refIDAttribute = refIDAttribute
98 | }
99 |
100 | func (s *Signer) setDigest() (err error) {
101 | references := s.signedInfo.FindElements("./Reference")
102 | for _, ref := range references {
103 | doc := s.xml.Copy()
104 |
105 | transforms := ref.SelectElement("Transforms")
106 | if transforms != nil {
107 | for _, transform := range transforms.SelectElements("Transform") {
108 | doc, err = processTransform(transform, doc, ALL_TRANSFORMS)
109 | if err != nil {
110 | return err
111 | }
112 | }
113 | }
114 |
115 | doc, err := s.getReferencedXML(ref, doc)
116 | if err != nil {
117 | return err
118 | }
119 |
120 | calculatedValue, err := calculateHash(ref, doc)
121 | if err != nil {
122 | return err
123 | }
124 |
125 | digestValueElement := ref.SelectElement("DigestValue")
126 | if digestValueElement == nil {
127 | return errors.New("signedxml: unable to find DigestValue")
128 | }
129 | digestValueElement.SetText(calculatedValue)
130 | }
131 | return nil
132 | }
133 |
134 | func (s *Signer) setSignature() error {
135 | canonSignedInfo, err := s.canonAlgorithm.ProcessElement(s.signedInfo, "")
136 | if err != nil {
137 | return err
138 | }
139 |
140 | var hashed, signature []byte
141 | //var h1, h2 *big.Int
142 | signingAlgorithm, ok := signingAlgorithms[s.sigAlgorithm]
143 | if !ok {
144 | return errors.New("signedxml: unsupported algorithm")
145 | }
146 |
147 | hasher := signingAlgorithm.hash.New()
148 | hasher.Write([]byte(canonSignedInfo))
149 | hashed = hasher.Sum(nil)
150 |
151 | switch signingAlgorithm.algorithm {
152 | case "rsa":
153 | pk, ok := s.privateKey.(*rsa.PrivateKey)
154 | if !ok {
155 | return fmt.Errorf("unexpected %T (expected *rsa.PrivateKey)", s.privateKey)
156 | }
157 | signature, err = rsa.SignPKCS1v15(rand.Reader, pk, signingAlgorithm.hash, hashed)
158 | /*
159 | case "dsa":
160 | h1, h2, err = dsa.Sign(rand.Reader, s.privateKey.(*dsa.PrivateKey), hashed)
161 | case "ecdsa":
162 | h1, h2, err = ecdsa.Sign(rand.Reader, s.privateKey.(*ecdsa.PrivateKey), hashed)
163 | */
164 | }
165 | if err != nil {
166 | return err
167 | }
168 |
169 | // DSA and ECDSA has not been validated
170 | /*
171 | if signature == nil && h1 != nil && h2 != nil {
172 | signature = append(h1.Bytes(), h2.Bytes()...)
173 | }
174 | */
175 |
176 | b64 := base64.StdEncoding.EncodeToString(signature)
177 | sigValueElement := s.signature.SelectElement("SignatureValue")
178 | sigValueElement.SetText(b64)
179 |
180 | return nil
181 | }
182 |
--------------------------------------------------------------------------------
/validator.go:
--------------------------------------------------------------------------------
1 | package signedxml
2 |
3 | import (
4 | "crypto/x509"
5 | "encoding/base64"
6 | "errors"
7 | "fmt"
8 | "log"
9 |
10 | "github.com/beevik/etree"
11 | )
12 |
13 | // Validator provides options for verifying a signed XML document
14 | type Validator struct {
15 | Certificates []x509.Certificate
16 | signingCert x509.Certificate
17 | signatureData
18 | }
19 |
20 | // NewValidator returns a *Validator for the XML provided
21 | func NewValidator(xml string) (*Validator, error) {
22 | doc, err := parseXML(xml)
23 | if err != nil {
24 | return nil, err
25 | }
26 | v := &Validator{signatureData: signatureData{xml: doc}}
27 | return v, nil
28 | }
29 |
30 | // SetReferenceIDAttribute set the referenceIDAttribute
31 | func (v *Validator) SetReferenceIDAttribute(refIDAttribute string) {
32 | v.signatureData.refIDAttribute = refIDAttribute
33 | }
34 |
35 | // SetXML is used to assign the XML document that the Validator will verify
36 | func (v *Validator) SetXML(xml string) error {
37 | doc := etree.NewDocument()
38 | err := doc.ReadFromString(xml)
39 | v.xml = doc
40 | return err
41 | }
42 |
43 | // SigningCert returns the certificate, if any, that was used to successfully
44 | // validate the signature of the XML document. This will be a zero value
45 | // x509.Certificate before Validator.Validate is successfully called.
46 | func (v *Validator) SigningCert() x509.Certificate {
47 | return v.signingCert
48 | }
49 |
50 | // ValidateReferences validates the Reference digest values, and the signature value
51 | // over the SignedInfo.
52 | //
53 | // If the signature is enveloped in the XML, then it will be used.
54 | // Otherwise, an external signature should be assigned using
55 | // Validator.SetSignature.
56 | //
57 | // The references returned contain validated XML from the signature and must be used.
58 | // Callers that ignore the returned references are vulnerable to XML injection.
59 | func (v *Validator) ValidateReferences() ([]string, error) {
60 | if err := v.loadValuesFromXML(); err != nil {
61 | return nil, err
62 | }
63 |
64 | referenced, err := v.validateReferences()
65 | if err != nil {
66 | return nil, err
67 | }
68 |
69 | var ref []string
70 | for _, doc := range referenced {
71 | docStr, err := doc.WriteToString()
72 | if err != nil {
73 | return nil, err
74 | }
75 | ref = append(ref, docStr)
76 | }
77 |
78 | err = v.validateSignature()
79 | return ref, err
80 | }
81 |
82 | func (v *Validator) loadValuesFromXML() error {
83 | if v.signature == nil {
84 | if err := v.parseEnvelopedSignature(); err != nil {
85 | return err
86 | }
87 | }
88 | if err := v.parseSignedInfo(); err != nil {
89 | return err
90 | }
91 | if err := v.parseSigValue(); err != nil {
92 | return err
93 | }
94 | if err := v.parseSigAlgorithm(); err != nil {
95 | return err
96 | }
97 | if err := v.parseCanonAlgorithm(); err != nil {
98 | return err
99 | }
100 | if err := v.loadCertificates(); err != nil {
101 | return err
102 | }
103 | return nil
104 | }
105 |
106 | func (v *Validator) validateReferences() (referenced []*etree.Document, err error) {
107 | references := v.signedInfo.FindElements("./Reference")
108 | for _, ref := range references {
109 | doc := v.xml.Copy()
110 |
111 | transforms := ref.SelectElement("Transforms")
112 | if transforms != nil {
113 | for _, transform := range transforms.SelectElements("Transform") {
114 | doc, err = processTransform(transform, doc, ALL_TRANSFORMS)
115 | if err != nil {
116 | return nil, err
117 | }
118 | }
119 | }
120 |
121 | refUri := ref.SelectAttrValue("URI", "")
122 | doc, err = v.getReferencedXML(ref, doc)
123 | if err != nil {
124 | return nil, err
125 | }
126 |
127 | if transforms != nil {
128 | for _, transform := range transforms.SelectElements("Transform") {
129 | doc, err = processTransform(transform, doc, "c14n")
130 | if err != nil {
131 | return nil, err
132 | }
133 | }
134 | }
135 |
136 | referenced = append(referenced, doc)
137 |
138 | digestValueElement := ref.SelectElement("DigestValue")
139 | if digestValueElement == nil {
140 | return nil, fmt.Errorf("signedxml [%s]: unable to find DigestValue", refUri)
141 | }
142 | digestValue := digestValueElement.Text()
143 | calculatedValue, err := calculateHash(ref, doc)
144 | if err != nil {
145 | return nil, err
146 | }
147 |
148 | if calculatedValue != digestValue {
149 | return nil, fmt.Errorf("signedxml [%s]: Calculated digest (%s) does not match the"+
150 | " expected digestvalue of %s", refUri, calculatedValue, digestValue)
151 | }
152 | }
153 | return referenced, nil
154 | }
155 |
156 | func (v *Validator) validateSignature() error {
157 | canonSignedInfo, err := v.canonAlgorithm.ProcessElement(v.signedInfo, "")
158 | if err != nil {
159 | return err
160 | }
161 |
162 | b64, err := base64.StdEncoding.DecodeString(v.sigValue)
163 | if err != nil {
164 | return err
165 | }
166 | sig := []byte(b64)
167 |
168 | v.signingCert = x509.Certificate{}
169 | for _, cert := range v.Certificates {
170 | err := cert.CheckSignature(v.sigAlgorithm, []byte(canonSignedInfo), sig)
171 | if err == nil {
172 | v.signingCert = cert
173 | return nil
174 | }
175 | }
176 |
177 | return errors.New("signedxml: Calculated signature does not match the " +
178 | "SignatureValue provided")
179 | }
180 |
181 | func (v *Validator) loadCertificates() error {
182 | // If v.Certificates is already populated, then the client has already set it
183 | // to the desired cert. Otherwise, let's pull the public keys from the XML
184 | if len(v.Certificates) < 1 {
185 | keydata := v.xml.FindElements(".//X509Certificate")
186 | for _, key := range keydata {
187 | cert, err := getCertFromPEMString(key.Text())
188 | if err != nil {
189 | log.Printf("signedxml: Unable to load certificate: (%s). "+
190 | "Looking for another cert.", err)
191 | } else {
192 | v.Certificates = append(v.Certificates, *cert)
193 | }
194 | }
195 | }
196 |
197 | if len(v.Certificates) < 1 {
198 | return errors.New("signedxml: a certificate is required, but was not found")
199 | }
200 | return nil
201 | }
202 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | [](https://github.com/moov-io)
2 |
3 | ## moov-io/signedxml
4 |
5 | [](https://godoc.org/github.com/moov-io/signedxml)
6 | [](https://github.com/moov-io/signedxml/actions)
7 | [](https://codecov.io/gh/moov-io/signedxml)
8 | [](https://goreportcard.com/report/github.com/moov-io/signedxml)
9 | [](https://github.com/moov-io/signedxml)
10 | [](https://raw.githubusercontent.com/moov-io/signedxml/master/LICENSE.md)
11 | [](https://slack.moov.io/)
12 | [](https://twitter.com/moov?lang=en)
13 |
14 | The signedxml package transforms and validates signed xml documents. The main use case is to support Single Sign On protocols like SAML and WS-Federation.
15 |
16 | Other packages that provide similar functionality rely on C libraries, which makes them difficult to run across platforms without significant configuration. `signedxml` is written in pure go, and can be easily used on any platform. This package was originally created by [Matt Smith](https://github.com/ma314smith) and is in use at Moov Financial.
17 |
18 | ### Install
19 |
20 | `go get github.com/moov-io/signedxml`
21 |
22 | ### Included Algorithms
23 |
24 | - Hashes
25 | - http://www.w3.org/2001/04/xmldsig-more#md5
26 | - http://www.w3.org/2000/09/xmldsig#sha1
27 | - http://www.w3.org/2001/04/xmldsig-more#sha224
28 | - http://www.w3.org/2001/04/xmlenc#sha256
29 | - http://www.w3.org/2001/04/xmldsig-more#sha384
30 | - http://www.w3.org/2001/04/xmlenc#sha512
31 | - http://www.w3.org/2001/04/xmlenc#ripemd160
32 |
33 |
34 | - Signatures
35 | - http://www.w3.org/2001/04/xmldsig-more#rsa-md2
36 | - http://www.w3.org/2001/04/xmldsig-more#rsa-md5
37 | - http://www.w3.org/2000/09/xmldsig#rsa-sha1
38 | - http://www.w3.org/2001/04/xmldsig-more#rsa-sha256
39 | - http://www.w3.org/2001/04/xmldsig-more#rsa-sha384
40 | - http://www.w3.org/2001/04/xmldsig-more#rsa-sha512
41 | - http://www.w3.org/2000/09/xmldsig#dsa-sha1
42 | - http://www.w3.org/2000/09/xmldsig#dsa-sha256
43 | - http://www.w3.org/2001/04/xmldsig-more#ecdsa-sha1
44 | - http://www.w3.org/2001/04/xmldsig-more#ecdsa-sha256
45 | - http://www.w3.org/2001/04/xmldsig-more#ecdsa-sha384
46 | - http://www.w3.org/2001/04/xmldsig-more#ecdsa-sha512
47 |
48 |
49 | - Canonicalization Methods/Transforms
50 | - http://www.w3.org/2000/09/xmldsig#enveloped-signature
51 | - http://www.w3.org/TR/2001/REC-xml-c14n-20010315
52 | - http://www.w3.org/2001/10/xml-exc-c14n#
53 | - http://www.w3.org/2001/10/xml-exc-c14n#WithComments
54 |
55 | ### Examples
56 |
57 | #### Validating signed XML
58 | If your signed xml contains the signature and certificate, then you can just pass in the xml and call `ValidateReferences()`.
59 | ```go
60 | validator, err := signedxml.NewValidator(``)
61 | xml, err = validator.ValidateReferences()
62 | ```
63 | `ValidateReferences()` verifies the DigestValue and SignatureValue in the xml document, and returns the signed payload(s). If the error value is `nil`, then the signed xml is valid.
64 |
65 | The x509.Certificate that was successfully used to validate the xml will be available by calling:
66 | ```go
67 | validator.SigningCert()
68 | ```
69 | You can then verify that you trust the certificate. You can optionally supply your trusted certificates ahead of time by assigning them to the `Certificates` property of the `Validator` object, which is an x509.Certificate array.
70 |
71 | #### Using an external Signature
72 | If you need to specify an external Signature, you can use the `SetSignature()` function to assign it:
73 | ```go
74 | validator.SetSignature(<`Signature>`)
75 | ```
76 |
77 | #### Generating signed XML
78 | It is expected that your XML contains the Signature element with all the parameters set (except DigestValue and SignatureValue).
79 | ```go
80 | signer, err := signedxml.NewSigner(`https://openidp.feide.no
2 |
3 |
4 | fc21hh1bKZpaMNjx9HfOfVelfWw=dkONrkxW+LSuDvnNMG/mWYFa47d2WGyapLhXSTYqrlT9Td+tT7ciojNJ55WTaPaCMt7IrGtIxxskPAZIjdIn5pRyDxHr0joWxzZ7oZHCOI1CnQV5HjOq+rzzmEN2LctCZ6S4hbL7SQ1qJ3vp2BCXAygy4tmJOURQdnk0KLwwRS8=
5 | MIICizCCAfQCCQCY8tKaMc0BMjANBgkqhkiG9w0BAQUFADCBiTELMAkGA1UEBhMCTk8xEjAQBgNVBAgTCVRyb25kaGVpbTEQMA4GA1UEChMHVU5JTkVUVDEOMAwGA1UECxMFRmVpZGUxGTAXBgNVBAMTEG9wZW5pZHAuZmVpZGUubm8xKTAnBgkqhkiG9w0BCQEWGmFuZHJlYXMuc29sYmVyZ0B1bmluZXR0Lm5vMB4XDTA4MDUwODA5MjI0OFoXDTM1MDkyMzA5MjI0OFowgYkxCzAJBgNVBAYTAk5PMRIwEAYDVQQIEwlUcm9uZGhlaW0xEDAOBgNVBAoTB1VOSU5FVFQxDjAMBgNVBAsTBUZlaWRlMRkwFwYDVQQDExBvcGVuaWRwLmZlaWRlLm5vMSkwJwYJKoZIhvcNAQkBFhphbmRyZWFzLnNvbGJlcmdAdW5pbmV0dC5ubzCBnzANBgkqhkiG9w0BAQEFAAOBjQAwgYkCgYEAt8jLoqI1VTlxAZ2axiDIThWcAOXdu8KkVUWaN/SooO9O0QQ7KRUjSGKN9JK65AFRDXQkWPAu4HlnO4noYlFSLnYyDxI66LCr71x4lgFJjqLeAvB/GqBqFfIZ3YK/NrhnUqFwZu63nLrZjcUZxNaPjOOSRSDaXpv1kb5k3jOiSGECAwEAATANBgkqhkiG9w0BAQUFAAOBgQBQYj4cAafWaYfjBU2zi1ElwStIaJ5nyp/s/8B8SAPK2T79McMyccP3wSW13LHkmM1jwKe3ACFXBvqGQN0IbcH49hu0FKhYFM/GPDJcIHFBsiyMBXChpye9vBaTNEBCtU3KjjyG0hRT2mAQ9h+bkPmOvlEo/aH0xR68Z9hw4PF13w==https://openidp.feide.no
6 |
7 |
8 | RnNjoyUguwze5w2R+cboyTHlkQk=aw5711jKP7xragunjRRCAD4mT4xKHc37iohBpQDbdSomD3ksOSB96UZQp0MtaC3xlVSkMtYw85Om96T2q2xrxLLYVA50eFJEMMF7SCVPStWTVjBlaCuOPEQxIaHyJs9Sy3MCEfbBh4Pqn9IJBd1kzwdlCrWWjAmksbFFg5wHQJA=
9 | MIICizCCAfQCCQCY8tKaMc0BMjANBgkqhkiG9w0BAQUFADCBiTELMAkGA1UEBhMCTk8xEjAQBgNVBAgTCVRyb25kaGVpbTEQMA4GA1UEChMHVU5JTkVUVDEOMAwGA1UECxMFRmVpZGUxGTAXBgNVBAMTEG9wZW5pZHAuZmVpZGUubm8xKTAnBgkqhkiG9w0BCQEWGmFuZHJlYXMuc29sYmVyZ0B1bmluZXR0Lm5vMB4XDTA4MDUwODA5MjI0OFoXDTM1MDkyMzA5MjI0OFowgYkxCzAJBgNVBAYTAk5PMRIwEAYDVQQIEwlUcm9uZGhlaW0xEDAOBgNVBAoTB1VOSU5FVFQxDjAMBgNVBAsTBUZlaWRlMRkwFwYDVQQDExBvcGVuaWRwLmZlaWRlLm5vMSkwJwYJKoZIhvcNAQkBFhphbmRyZWFzLnNvbGJlcmdAdW5pbmV0dC5ubzCBnzANBgkqhkiG9w0BAQEFAAOBjQAwgYkCgYEAt8jLoqI1VTlxAZ2axiDIThWcAOXdu8KkVUWaN/SooO9O0QQ7KRUjSGKN9JK65AFRDXQkWPAu4HlnO4noYlFSLnYyDxI66LCr71x4lgFJjqLeAvB/GqBqFfIZ3YK/NrhnUqFwZu63nLrZjcUZxNaPjOOSRSDaXpv1kb5k3jOiSGECAwEAATANBgkqhkiG9w0BAQUFAAOBgQBQYj4cAafWaYfjBU2zi1ElwStIaJ5nyp/s/8B8SAPK2T79McMyccP3wSW13LHkmM1jwKe3ACFXBvqGQN0IbcH49hu0FKhYFM/GPDJcIHFBsiyMBXChpye9vBaTNEBCtU3KjjyG0hRT2mAQ9h+bkPmOvlEo/aH0xR68Z9hw4PF13w==_6c5dcaa3053321ff4d63785fbc3f67c59a129cde82passport-samlurn:oasis:names:tc:SAML:2.0:ac:classes:PasswordbergieHenriBergiusHenri Bergiushenri.bergius@nemein.combergie@rnd.feide.no8216c78fe244502efa13f62e6615c94acb7bdf3ebergieHenriBergiusHenri Bergiushenri.bergius@nemein.combergie@rnd.feide.no8216c78fe244502efa13f62e6615c94acb7bdf3e
10 |
--------------------------------------------------------------------------------
/examples/canonicalization_test.go:
--------------------------------------------------------------------------------
1 | package examples
2 |
3 | import (
4 | "testing"
5 |
6 | "github.com/leifj/signedxml"
7 |
8 | . "github.com/smartystreets/goconvey/convey"
9 | )
10 |
11 | var example31Input = `
12 |
13 |
14 |
15 |
16 |
17 | Hello, world!
18 |
19 |
20 |
21 |
22 |
23 | `
24 |
25 | var example31Output = `
26 | Hello, world!
27 | `
28 |
29 | var example31OutputWithComments = `
30 | Hello, world!
31 |
32 |
33 | `
34 |
35 | var example32Input = `
36 |
37 | A B
38 |
39 | A
40 |
41 | B
42 | A B
43 | C
44 |
45 | `
46 |
47 | var example32Output = `
48 |
49 | A B
50 |
51 | A
52 |
53 | B
54 | A B
55 | C
56 |
57 | `
58 |
59 | var example33Input = `]>
60 |
61 |
62 |
63 |
64 |
65 |
66 |
67 |
68 |
69 |
70 |
71 |
72 |
73 | `
74 |
75 | var example33Output = `
76 |
77 |
78 |
79 |
80 |
81 |
82 |
83 |
84 |
85 |
86 |
87 |
88 | `
89 |
90 | var example34Input = `
92 |
93 | ]>
94 |
95 | First line
Second line
96 | 2
97 | "0" && value<"10" ?"valid":"error"]]>
98 | valid
99 |
100 |
101 | `
102 |
103 | var example34Output = `
104 | First line
105 | Second line
106 | 2
107 | value>"0" && value<"10" ?"valid":"error"
108 | valid
109 |
110 |
111 | `
112 |
113 | // modified to not include DTD processing. still tests for whitespace treated as
114 | // CDATA
115 | var example34ModifiedOutput = `
116 | First line
117 | Second line
118 | 2
119 | value>"0" && value<"10" ?"valid":"error"
120 | valid
121 |
122 |
123 | `
124 |
125 | var example35Input = `
127 |
128 |
129 |
130 |
131 | ]>
132 |
133 | &ent1;, &ent2;!
134 |
135 |
136 | `
137 |
138 | var example35Output = `
139 | Hello, world!
140 | `
141 |
142 | var example36Input = `
143 | ©`
144 |
145 | var example36Output = "\u00A9"
146 |
147 | var example37Input = `
149 |
150 | ]>
151 |
152 |
153 |
154 |
155 |
156 |
157 | `
158 |
159 | var example37SubsetExpression = `
160 |
161 | (//. | //@* | //namespace::*)
162 | [
163 | self::ietf:e1 or (parent::ietf:e1 and not(self::text() or self::e2))
164 | or
165 | count(id("E3")|ancestor-or-self::node()) = count(ancestor-or-self::node())
166 | ]`
167 |
168 | var example37Output = ``
169 |
170 | var exampleGHIssue50Input = ``
171 | var exampleGHIssue50Output = ``
172 |
173 | type exampleXML struct {
174 | input string
175 | output string
176 | withComments bool
177 | expression string
178 | }
179 |
180 | // test examples from the spec (www.w3.org/TR/2001/REC-xml-c14n-20010315#Examples)
181 | func TestCanonicalizationExamples(t *testing.T) {
182 | Convey("Given XML Input", t, func() {
183 | cases := map[string]exampleXML{
184 | "(Example 3.1 w/o Comments)": {input: example31Input, output: example31Output},
185 | "(Example 3.1 w/Comments)": {input: example31Input, output: example31OutputWithComments, withComments: true},
186 | "(Example 3.2)": {input: example32Input, output: example32Output},
187 | // 3.3 is for Canonical NOT ExclusiveCanonical (one of the exceptions here: http://www.w3.org/TR/xml-exc-c14n/#sec-Specification)
188 | // "(Example 3.3)": {input: example33Input, output: example33Output},
189 | "(Example 3.4)": {input: example34Input, output: example34ModifiedOutput},
190 | // "(Example 3.5)": {input: example35Input, output: example35Output},
191 | // 3.6 will work, but requires a change to the etree package first:
192 | // http://stackoverflow.com/questions/6002619/unmarshal-an-iso-8859-1-xml-input-in-go
193 | "(Example 3.6)": {input: example36Input, output: example36Output},
194 | // "(Example 3.7)": {input: example37Input, output: example37Output, expression: example37SubsetExpression},
195 | "(Example from GitHub Issue #50)": {input: exampleGHIssue50Input, output: exampleGHIssue50Output},
196 | }
197 | for description, test := range cases {
198 | Convey("When transformed "+description, func() {
199 | transform := signedxml.ExclusiveCanonicalization{
200 | WithComments: test.withComments,
201 | }
202 | resultXML, err := transform.Process(test.input, "")
203 | Convey("Then the resulting XML match the example output", func() {
204 | So(err, ShouldBeNil)
205 | So(resultXML, ShouldEqual, test.output)
206 | })
207 | })
208 | }
209 | })
210 | }
211 |
212 | func TestTODO(t *testing.T) {
213 | // The XML specifications cover the following examples, but our library does not successfully transform.
214 | t.Logf("Input:\n%s\nOutput:\n%s\n", example33Input, example33Output) // Example 3.3
215 | t.Logf("Input:\n%s\nOutput:\n%s\n", example35Input, example35Output) // Example 3.5
216 | t.Logf("Input:\n%s\nOutput:\n%s\n", example37Input, example37Output) // Example 3.7
217 | }
218 |
--------------------------------------------------------------------------------
/exclusivecanonicalization.go:
--------------------------------------------------------------------------------
1 | package signedxml
2 |
3 | import (
4 | "sort"
5 | "strings"
6 |
7 | "github.com/beevik/etree"
8 | )
9 |
10 | // the attribute and attributes structs are used to implement the sort.Interface
11 | type attribute struct {
12 | prefix, uri, key, value string
13 | }
14 |
15 | type attributes []attribute
16 |
17 | func (a attributes) Len() int {
18 | return len(a)
19 | }
20 |
21 | // Less is part of the sort.Interface, and is used to order attributes by their
22 | // namespace URIs and then by their keys.
23 | func (a attributes) Less(i, j int) bool {
24 | if a[i].uri == "" && a[j].uri != "" {
25 | return true
26 | }
27 | if a[j].uri == "" && a[i].uri != "" {
28 | return false
29 | }
30 |
31 | iQual := a[i].uri + a[i].key
32 | jQual := a[j].uri + a[j].key
33 |
34 | return iQual < jQual
35 | }
36 |
37 | func (a attributes) Swap(i, j int) {
38 | a[i], a[j] = a[j], a[i]
39 | }
40 |
41 | // ExclusiveCanonicalization implements the CanonicalizationAlgorithm
42 | // interface and is used for processing the
43 | // http://www.w3.org/2001/10/xml-exc-c14n# and
44 | // http://www.w3.org/2001/10/xml-exc-c14n#WithComments transform
45 | // algorithms
46 | type ExclusiveCanonicalization struct {
47 | WithComments bool
48 | inclusiveNamespacePrefixList []string
49 | namespaces map[string]string
50 | }
51 |
52 | // see CanonicalizationAlgorithm.ProcessElement
53 | func (e ExclusiveCanonicalization) ProcessElement(inputXML *etree.Element, transformXML string) (outputXML string, err error) {
54 | doc := etree.NewDocument()
55 | doc.SetRoot(inputXML.Copy())
56 | return e.processDocument(doc, transformXML)
57 | }
58 |
59 | // see CanonicalizationAlgorithm.ProcessDocument
60 | func (e ExclusiveCanonicalization) ProcessDocument(doc *etree.Document,
61 | transformXML string) (outputXML string, err error) {
62 |
63 | return e.processDocument(doc.Copy(), transformXML)
64 | }
65 |
66 | // see CanonicalizationAlgorithm.Process
67 | func (e ExclusiveCanonicalization) Process(inputXML string, transformXML string) (outputXML string, err error) {
68 | doc := etree.NewDocument()
69 | err = doc.ReadFromString(inputXML)
70 | if err != nil {
71 | return "", err
72 | }
73 | return e.ProcessDocument(doc, transformXML)
74 | }
75 |
76 | func (e ExclusiveCanonicalization) processDocument(doc *etree.Document, transformXML string) (outputXML string, err error) {
77 | e.namespaces = make(map[string]string)
78 |
79 | doc.WriteSettings.CanonicalEndTags = true
80 | doc.WriteSettings.CanonicalText = true
81 | doc.WriteSettings.CanonicalAttrVal = true
82 |
83 | e.loadPrefixList(transformXML)
84 | e.processDocLevelNodes(doc)
85 | e.processRecursive(doc.Root(), nil, "")
86 |
87 | outputXML, err = doc.WriteToString()
88 | return outputXML, err
89 | }
90 |
91 | func (e *ExclusiveCanonicalization) loadPrefixList(transformXML string) {
92 | if transformXML != "" {
93 | tDoc := etree.NewDocument()
94 | tDoc.ReadFromString(transformXML)
95 | inclNSNode := tDoc.Root().SelectElement("InclusiveNamespaces")
96 | if inclNSNode != nil {
97 | prefixList := inclNSNode.SelectAttrValue("PrefixList", "")
98 | if prefixList != "" {
99 | e.inclusiveNamespacePrefixList = strings.Split(prefixList, " ")
100 | }
101 | }
102 | }
103 | }
104 |
105 | // process nodes outside of the root element
106 | func (e ExclusiveCanonicalization) processDocLevelNodes(doc *etree.Document) {
107 | // keep track of the previous node action to manage line returns in CharData
108 | previousNodeRemoved := false
109 |
110 | for i := 0; i < len(doc.Child); i++ {
111 | c := doc.Child[i]
112 |
113 | switch c := c.(type) {
114 | case *etree.Comment:
115 | if e.WithComments {
116 | previousNodeRemoved = false
117 | } else {
118 | removeTokenFromDocument(c, doc)
119 | i--
120 | previousNodeRemoved = true
121 | }
122 | case *etree.CharData:
123 | if isWhitespace(c.Data) {
124 | if previousNodeRemoved {
125 | removeTokenFromDocument(c, doc)
126 | i--
127 | previousNodeRemoved = true
128 | } else {
129 | c.Data = "\n"
130 | }
131 |
132 | }
133 | case *etree.Directive:
134 | removeTokenFromDocument(c, doc)
135 | i--
136 | previousNodeRemoved = true
137 | case *etree.ProcInst:
138 | // remove declaration, but leave other PI's
139 | if c.Target == "xml" {
140 | removeTokenFromDocument(c, doc)
141 | i--
142 | previousNodeRemoved = true
143 | } else {
144 | previousNodeRemoved = false
145 | }
146 | default:
147 | previousNodeRemoved = false
148 | }
149 | }
150 |
151 | // if the last line is CharData whitespace, then remove it
152 | if c, ok := doc.Child[len(doc.Child)-1].(*etree.CharData); ok {
153 | if isWhitespace(c.Data) {
154 | removeTokenFromDocument(c, doc)
155 | }
156 | }
157 | }
158 |
159 | func (e ExclusiveCanonicalization) processRecursive(node *etree.Element,
160 | prefixesInScope []string, defaultNS string) {
161 |
162 | newDefaultNS, newPrefixesInScope := e.renderAttributes(node, prefixesInScope, defaultNS)
163 |
164 | for i := 0; i < len(node.Child); i++ {
165 | child := node.Child[i]
166 |
167 | oldNamespaces := e.namespaces
168 | e.namespaces = copyNamespace(oldNamespaces)
169 |
170 | switch child := child.(type) {
171 | case *etree.Comment:
172 | if !e.WithComments {
173 | removeTokenFromElement(etree.Token(child), node)
174 | i--
175 | }
176 | case *etree.Element:
177 | e.processRecursive(child, newPrefixesInScope, newDefaultNS)
178 | }
179 |
180 | e.namespaces = oldNamespaces
181 | }
182 | }
183 |
184 | func (e ExclusiveCanonicalization) renderAttributes(node *etree.Element, prefixesInScope []string, defaultNS string) (newDefaultNS string, newPrefixesInScope []string) {
185 | currentNS := node.SelectAttrValue("xmlns", defaultNS)
186 | elementAttributes := []etree.Attr{}
187 | nsListToRender := make(map[string]string)
188 | attrListToRender := attributes{}
189 |
190 | // load map with for prefix -> uri lookup
191 | for _, attr := range node.Attr {
192 | if attr.Space == "xmlns" {
193 | e.namespaces[attr.Key] = attr.Value
194 | }
195 | }
196 |
197 | // handle the namespace of the node itself
198 | if node.Space != "" {
199 | if !contains(prefixesInScope, node.Space) {
200 | nsListToRender["xmlns:"+node.Space] = e.namespaces[node.Space]
201 | prefixesInScope = append(prefixesInScope, node.Space)
202 | }
203 | } else if defaultNS != currentNS {
204 | elementAttributes = append(elementAttributes, etree.Attr{Key: "xmlns", Value: currentNS})
205 | }
206 |
207 | for _, attr := range node.Attr {
208 | // include the namespaces if they are in the inclusiveNamespacePrefixList
209 | if attr.Space == "xmlns" {
210 | if !contains(prefixesInScope, attr.Key) &&
211 | contains(e.inclusiveNamespacePrefixList, attr.Key) {
212 |
213 | nsListToRender["xmlns:"+attr.Key] = attr.Value
214 | prefixesInScope = append(prefixesInScope, attr.Key)
215 | }
216 | }
217 |
218 | // include namespaces for qualfied attributes
219 | if attr.Space != "" &&
220 | attr.Space != "xmlns" &&
221 | !contains(prefixesInScope, attr.Space) {
222 |
223 | if attr.Space != "xml" {
224 | nsListToRender["xmlns:"+attr.Space] = e.namespaces[attr.Space]
225 | }
226 |
227 | prefixesInScope = append(prefixesInScope, attr.Space)
228 | }
229 |
230 | // inclued all non-namespace attributes
231 | if attr.Space != "xmlns" && attr.Key != "xmlns" {
232 | attrListToRender = append(attrListToRender,
233 | attribute{
234 | prefix: attr.Space,
235 | uri: e.namespaces[attr.Space],
236 | key: attr.Key,
237 | value: attr.Value,
238 | })
239 | }
240 | }
241 |
242 | // sort and add the namespace attributes first
243 | sortedNSList := getSortedNamespaces(nsListToRender)
244 | elementAttributes = append(elementAttributes, sortedNSList...)
245 | // then sort and add the non-namespace attributes
246 | sortedAttributes := getSortedAttributes(attrListToRender)
247 | elementAttributes = append(elementAttributes, sortedAttributes...)
248 | // replace the nodes attributes with the sorted copy
249 | node.Attr = elementAttributes
250 | return currentNS, prefixesInScope
251 | }
252 |
253 | func contains(slice []string, value string) bool {
254 | for _, s := range slice {
255 | if s == value {
256 | return true
257 | }
258 | }
259 | return false
260 | }
261 |
262 | // getSortedNamespaces sorts the namespace attributes by their prefix
263 | func getSortedNamespaces(list map[string]string) []etree.Attr {
264 | var keys []string
265 | for k := range list {
266 | keys = append(keys, k)
267 | }
268 | sort.Strings(keys)
269 |
270 | elem := etree.Element{}
271 | for _, k := range keys {
272 | elem.CreateAttr(k, list[k])
273 | }
274 |
275 | return elem.Attr
276 | }
277 |
278 | // getSortedAttributes sorts attributes by their namespace URIs
279 | func getSortedAttributes(list attributes) []etree.Attr {
280 | sort.Sort(list)
281 | attrs := make([]etree.Attr, len(list))
282 | for i, a := range list {
283 | attrs[i] = etree.Attr{
284 | Space: a.prefix,
285 | Key: a.key,
286 | Value: a.value,
287 | }
288 | }
289 | return attrs
290 | }
291 |
292 | func removeTokenFromElement(token etree.Token, e *etree.Element) *etree.Token {
293 | for i, t := range e.Child {
294 | if t == token {
295 | e.Child = append(e.Child[0:i], e.Child[i+1:]...)
296 | return &t
297 | }
298 | }
299 | return nil
300 | }
301 |
302 | func removeTokenFromDocument(token etree.Token, d *etree.Document) *etree.Token {
303 | for i, t := range d.Child {
304 | if t == token {
305 | d.Child = append(d.Child[0:i], d.Child[i+1:]...)
306 | return &t
307 | }
308 | }
309 | return nil
310 | }
311 |
312 | // isWhitespace returns true if the byte slice contains only
313 | // whitespace characters.
314 | func isWhitespace(s string) bool {
315 | for i := 0; i < len(s); i++ {
316 | if c := s[i]; c != ' ' && c != '\t' && c != '\n' && c != '\r' {
317 | return false
318 | }
319 | }
320 | return true
321 | }
322 |
323 | func copyNamespace(namespaces map[string]string) map[string]string {
324 | newVersion := map[string]string{}
325 | for index, element := range namespaces {
326 | newVersion[index] = element
327 | }
328 | return newVersion
329 | }
330 |
--------------------------------------------------------------------------------
/signedxml.go:
--------------------------------------------------------------------------------
1 | // Package signedxml transforms and validates signedxml documents
2 | package signedxml
3 |
4 | import (
5 | "crypto"
6 | "crypto/x509"
7 | "encoding/base64"
8 | "encoding/pem"
9 | "errors"
10 | "fmt"
11 | "log"
12 | "strings"
13 |
14 | "github.com/beevik/etree"
15 | dsig "github.com/russellhaering/goxmldsig"
16 | )
17 |
18 | func init() {
19 | hashAlgorithms = map[string]crypto.Hash{
20 | "http://www.w3.org/2001/04/xmldsig-more#md5": crypto.MD5,
21 | "http://www.w3.org/2000/09/xmldsig#sha1": crypto.SHA1,
22 | "http://www.w3.org/2001/04/xmldsig-more#sha224": crypto.SHA224,
23 | "http://www.w3.org/2001/04/xmlenc#sha256": crypto.SHA256,
24 | "http://www.w3.org/2001/04/xmldsig-more#sha384": crypto.SHA384,
25 | "http://www.w3.org/2001/04/xmlenc#sha512": crypto.SHA512,
26 | "http://www.w3.org/2001/04/xmlenc#ripemd160": crypto.RIPEMD160,
27 | }
28 |
29 | signatureAlgorithms = map[string]x509.SignatureAlgorithm{
30 | "http://www.w3.org/2001/04/xmldsig-more#rsa-md2": x509.MD2WithRSA,
31 | "http://www.w3.org/2001/04/xmldsig-more#rsa-md5": x509.MD5WithRSA,
32 | "http://www.w3.org/2000/09/xmldsig#rsa-sha1": x509.SHA1WithRSA,
33 | "http://www.w3.org/2001/04/xmldsig-more#rsa-sha256": x509.SHA256WithRSA,
34 | "http://www.w3.org/2001/04/xmldsig-more#rsa-sha384": x509.SHA384WithRSA,
35 | "http://www.w3.org/2001/04/xmldsig-more#rsa-sha512": x509.SHA512WithRSA,
36 | "http://www.w3.org/2000/09/xmldsig#dsa-sha1": x509.DSAWithSHA1,
37 | "http://www.w3.org/2000/09/xmldsig#dsa-sha256": x509.DSAWithSHA256,
38 | "http://www.w3.org/2001/04/xmldsig-more#ecdsa-sha1": x509.ECDSAWithSHA1,
39 | "http://www.w3.org/2001/04/xmldsig-more#ecdsa-sha256": x509.ECDSAWithSHA256,
40 | "http://www.w3.org/2001/04/xmldsig-more#ecdsa-sha384": x509.ECDSAWithSHA384,
41 | "http://www.w3.org/2001/04/xmldsig-more#ecdsa-sha512": x509.ECDSAWithSHA512,
42 | }
43 |
44 | CanonicalizationAlgorithms = map[string]CanonicalizationAlgorithm{
45 | "http://www.w3.org/2000/09/xmldsig#enveloped-signature": EnvelopedSignature{},
46 | "http://www.w3.org/TR/2001/REC-xml-c14n-20010315": ExclusiveCanonicalization{},
47 | "http://www.w3.org/2001/10/xml-exc-c14n#": ExclusiveCanonicalization{},
48 | "http://www.w3.org/2001/10/xml-exc-c14n#WithComments": ExclusiveCanonicalization{WithComments: true},
49 | dsig.CanonicalXML11AlgorithmId.String(): &c14N11Canonicalizer{},
50 | dsig.CanonicalXML11WithCommentsAlgorithmId.String(): &c14N11Canonicalizer{WithComments: true},
51 | dsig.CanonicalXML10RecAlgorithmId.String(): &c14N10RecCanonicalizer{},
52 | dsig.CanonicalXML10WithCommentsAlgorithmId.String(): &c14N10RecCanonicalizer{WithComments: true},
53 | }
54 | }
55 |
56 | // CanonicalizationAlgorithm defines an interface for processing an XML
57 | // document into a standard format.
58 | //
59 | // If any child elements are in the Transform node, the entire transform node
60 | // will be passed to the Process method through the transformXML parameter as an
61 | // XML string. This is necessary for transforms that need additional processing
62 | // data, like XPath (http://www.w3.org/TR/xmldsig-core/#sec-XPath). If there are
63 | // no child elements in Transform (or CanonicalizationMethod), then an empty
64 | // string will be passed through.
65 | type CanonicalizationAlgorithm interface {
66 | // ProcessElement is called to transform an XML Element within an XML Document
67 | // using the implementing algorithm
68 | ProcessElement(inputXML *etree.Element, transformXML string) (outputXML string, err error)
69 |
70 | // ProcessDocument is called to transform an XML Document using the implementing
71 | // algorithm.
72 | ProcessDocument(doc *etree.Document, transformXML string) (outputXML string, err error)
73 |
74 | // Process is called to transform a string containing XML text using the implementing
75 | // algorithm. The inputXML parameter should contain a complete XML Document. It is not
76 | // correct to use this function on XML fragments. Retained for backward comparability.
77 | // Use ProcessElement or ProcessDocument if possible.
78 | Process(inputXML string, transformXML string) (outputXML string, err error)
79 | }
80 |
81 | // CanonicalizationAlgorithms maps the CanonicalizationMethod or
82 | // Transform Algorithm URIs to a type that implements the
83 | // CanonicalizationAlgorithm interface.
84 | //
85 | // Implementations are provided for the following transforms:
86 | //
87 | // http://www.w3.org/2001/10/xml-exc-c14n# (ExclusiveCanonicalization)
88 | // http://www.w3.org/2001/10/xml-exc-c14n#WithComments (ExclusiveCanonicalizationWithComments)
89 | // http://www.w3.org/2000/09/xmldsig#enveloped-signature (EnvelopedSignature)
90 | //
91 | // Custom implementations can be added to the map
92 | var CanonicalizationAlgorithms map[string]CanonicalizationAlgorithm
93 | var hashAlgorithms map[string]crypto.Hash
94 | var signatureAlgorithms map[string]x509.SignatureAlgorithm
95 |
96 | // signatureData provides options for verifying a signed XML document
97 | type signatureData struct {
98 | xml *etree.Document
99 | signature *etree.Element
100 | signedInfo *etree.Element
101 | sigValue string
102 | sigAlgorithm x509.SignatureAlgorithm
103 | canonAlgorithm CanonicalizationAlgorithm
104 | refIDAttribute string
105 | }
106 |
107 | // SetSignature can be used to assign an external signature for the XML doc
108 | // that Validator will verify
109 | func (s *signatureData) SetSignature(sig string) error {
110 | doc := etree.NewDocument()
111 | err := doc.ReadFromString(sig)
112 | s.signature = doc.Root()
113 | return err
114 | }
115 |
116 | func (s *signatureData) parseEnvelopedSignature() error {
117 | sig := s.xml.FindElement("//Signature")
118 | if sig != nil {
119 | s.signature = sig
120 | } else {
121 | return errors.New("signedxml: Unable to find a unique signature element " +
122 | "in the xml document. The signature must either be enveloped in the " +
123 | "xml doc or externally assigned to Validator.SetSignature")
124 | }
125 | return nil
126 | }
127 |
128 | func (s *signatureData) parseSignedInfo() error {
129 | s.signedInfo = nil
130 | s.signedInfo = s.signature.SelectElement("SignedInfo")
131 | if s.signedInfo == nil {
132 | return errors.New("signedxml: unable to find SignedInfo element")
133 | }
134 |
135 | // move the Signature level namespace down to SignedInfo so that the signature
136 | // value will match up
137 | if s.signedInfo.Space != "" {
138 | attr := s.signature.SelectAttr(s.signedInfo.Space)
139 | if attr != nil {
140 | s.signedInfo.Attr = []etree.Attr{*attr}
141 | }
142 | } else {
143 | attr := s.signature.SelectAttr("xmlns")
144 | if attr != nil {
145 | s.signedInfo.Attr = []etree.Attr{*attr}
146 | }
147 | }
148 |
149 | // Copy SignedInfo xmlns: into itself if it does not exist and is defined as a root attribute
150 | root := s.xml.Root()
151 |
152 | if root != nil {
153 | sigNS := root.SelectAttr("xmlns:" + s.signedInfo.Space)
154 | if sigNS != nil {
155 | if s.signedInfo.SelectAttr("xmlns:"+s.signedInfo.Space) == nil {
156 | s.signedInfo.CreateAttr("xmlns:"+s.signedInfo.Space, sigNS.Value)
157 | }
158 | }
159 | }
160 |
161 | return nil
162 | }
163 |
164 | func (s *signatureData) parseSigValue() error {
165 | s.sigValue = ""
166 | sigValueElement := s.signature.SelectElement("SignatureValue")
167 | if sigValueElement != nil {
168 | s.sigValue = sigValueElement.Text()
169 | return nil
170 | }
171 | return errors.New("signedxml: unable to find SignatureValue")
172 | }
173 |
174 | func (s *signatureData) parseSigAlgorithm() error {
175 | s.sigAlgorithm = x509.UnknownSignatureAlgorithm
176 | sigMethod := s.signedInfo.SelectElement("SignatureMethod")
177 |
178 | var sigAlgoURI string
179 | if sigMethod == nil {
180 | return errors.New("signedxml: Unable to find SignatureMethod element")
181 | }
182 |
183 | sigAlgoURI = sigMethod.SelectAttrValue("Algorithm", "")
184 | if sigAlgoURI == "" {
185 | return errors.New("signedxml: Unable to find Algorithm in " +
186 | "SignatureMethod element")
187 | }
188 |
189 | sigAlgo, ok := signatureAlgorithms[sigAlgoURI]
190 | if ok {
191 | s.sigAlgorithm = sigAlgo
192 | return nil
193 | }
194 |
195 | return errors.New("signedxml: Unsupported Algorithm " + sigAlgoURI + " in " +
196 | "SignatureMethod")
197 | }
198 |
199 | func (s *signatureData) parseCanonAlgorithm() error {
200 | s.canonAlgorithm = nil
201 | canonMethod := s.signedInfo.SelectElement("CanonicalizationMethod")
202 |
203 | var canonAlgoURI string
204 | if canonMethod == nil {
205 | return errors.New("signedxml: Unable to find CanonicalizationMethod element")
206 | }
207 |
208 | canonAlgoURI = canonMethod.SelectAttrValue("Algorithm", "")
209 | if canonAlgoURI == "" {
210 | return errors.New("signedxml: Unable to find Algorithm in " +
211 | "CanonicalizationMethod element")
212 | }
213 |
214 | canonAlgo, ok := CanonicalizationAlgorithms[canonAlgoURI]
215 | if ok {
216 | s.canonAlgorithm = canonAlgo
217 | return nil
218 | }
219 |
220 | return errors.New("signedxml: Unsupported Algorithm " + canonAlgoURI + " in " +
221 | "CanonicalizationMethod")
222 | }
223 |
224 | func findNs(in *etree.Element, ns map[string]string) {
225 | ns[in.Space] = in.NamespaceURI()
226 | for _, c := range in.ChildElements() {
227 | findNs(c, ns)
228 | }
229 | }
230 |
231 | func findNamespaces(in *etree.Document) map[string]string {
232 | var ns = make(map[string]string)
233 | findNs(in.Root(), ns)
234 | return ns
235 | }
236 |
237 | func fixNs(e *etree.Element, ns map[string]string) {
238 | if e.NamespaceURI() == "" && e.Space != "" {
239 | if uri, ok := ns[e.Space]; ok {
240 | e.CreateAttr(fmt.Sprintf("xmlns:%s", e.Space), uri)
241 | } else {
242 | log.Printf("signedxml: Missing namespace tag %s\n", e.Space)
243 | }
244 | }
245 |
246 | for _, c := range e.ChildElements() {
247 | fixNs(c, ns)
248 | }
249 | }
250 |
251 | func fixNamespaces(in *etree.Document, out *etree.Document) {
252 | ns := findNamespaces(in)
253 | fixNs(out.Root(), ns)
254 | }
255 |
256 | func (s *signatureData) getReferencedXML(reference *etree.Element, inputDoc *etree.Document) (outputDoc *etree.Document, err error) {
257 | uri := reference.SelectAttrValue("URI", "")
258 | uri = strings.Replace(uri, "#", "", 1)
259 | // populate doc with the referenced xml from the Reference URI
260 | if uri == "" {
261 | outputDoc = inputDoc
262 | } else {
263 | refIDAttribute := "ID"
264 | if s.refIDAttribute != "" {
265 | refIDAttribute = s.refIDAttribute
266 | }
267 | path := fmt.Sprintf(".//[@%s='%s']", refIDAttribute, uri)
268 | e := inputDoc.FindElement(path)
269 | if e != nil {
270 | outputDoc = etree.NewDocument()
271 | outputDoc.SetRoot(e.Copy())
272 | } else {
273 | // SAML v1.1 Assertions use AssertionID
274 | path := fmt.Sprintf(".//[@AssertionID='%s']", uri)
275 | e := inputDoc.FindElement(path)
276 | if e != nil {
277 | outputDoc = etree.NewDocument()
278 | outputDoc.SetRoot(e.Copy())
279 | }
280 | }
281 | }
282 |
283 | if outputDoc == nil {
284 | return nil, errors.New("signedxml: unable to find refereced xml")
285 | }
286 |
287 | fixNamespaces(inputDoc, outputDoc)
288 |
289 | return outputDoc, nil
290 | }
291 |
292 | func getCertFromPEMString(pemString string) (*x509.Certificate, error) {
293 | pubkey := fmt.Sprintf("-----BEGIN PUBLIC KEY-----\n%s\n-----END PUBLIC KEY-----",
294 | pemString)
295 |
296 | pemBlock, _ := pem.Decode([]byte(pubkey))
297 | if pemBlock == nil {
298 | return &x509.Certificate{}, errors.New("Could not parse Public Key PEM")
299 | }
300 | if pemBlock.Type != "PUBLIC KEY" {
301 | return &x509.Certificate{}, errors.New("Found wrong key type")
302 | }
303 |
304 | cert, err := x509.ParseCertificate(pemBlock.Bytes)
305 | return cert, err
306 | }
307 |
308 | const ALL_TRANSFORMS string = ""
309 |
310 | func processTransform(transform *etree.Element,
311 | docIn *etree.Document, onlyIfContains string) (docOut *etree.Document, err error) {
312 |
313 | transformAlgoURI := transform.SelectAttrValue("Algorithm", "")
314 | if transformAlgoURI == "" {
315 | return nil, errors.New("signedxml: unable to find Algorithm in Transform")
316 | }
317 |
318 | if onlyIfContains == "" || strings.Contains(transformAlgoURI, onlyIfContains) {
319 |
320 | transformAlgo, ok := CanonicalizationAlgorithms[transformAlgoURI]
321 | if !ok {
322 | return nil, fmt.Errorf("signedxml: unable to find matching transform"+
323 | "algorithm for %s in CanonicalizationAlgorithms", transformAlgoURI)
324 | }
325 |
326 | var transformContent string
327 |
328 | if transform.ChildElements() != nil {
329 | tDoc := etree.NewDocument()
330 | tDoc.SetRoot(transform.Copy())
331 | transformContent, err = tDoc.WriteToString()
332 | if err != nil {
333 | return nil, err
334 | }
335 | }
336 |
337 | docString, err := transformAlgo.ProcessDocument(docIn, transformContent)
338 | if err != nil {
339 | return nil, err
340 | }
341 |
342 | docOut = etree.NewDocument()
343 | docOut.ReadFromString(docString)
344 | } else {
345 | docOut = docIn
346 | }
347 |
348 | return docOut, nil
349 | }
350 |
351 | func calculateHash(reference *etree.Element, doc *etree.Document) (string, error) {
352 | digestMethodElement := reference.SelectElement("DigestMethod")
353 | if digestMethodElement == nil {
354 | return "", errors.New("signedxml: unable to find DigestMethod")
355 | }
356 |
357 | digestMethodURI := digestMethodElement.SelectAttrValue("Algorithm", "")
358 | if digestMethodURI == "" {
359 | return "", errors.New("signedxml: unable to find Algorithm in DigestMethod")
360 | }
361 |
362 | digestAlgo, ok := hashAlgorithms[digestMethodURI]
363 | if !ok {
364 | return "", fmt.Errorf("signedxml: unable to find matching hash"+
365 | "algorithm for %s in hashAlgorithms", digestMethodURI)
366 | }
367 |
368 | doc.WriteSettings.CanonicalEndTags = true
369 | doc.WriteSettings.CanonicalText = true
370 | doc.WriteSettings.CanonicalAttrVal = true
371 |
372 | h := digestAlgo.New()
373 | docBytes, err := doc.WriteToBytes()
374 | if err != nil {
375 | return "", err
376 | }
377 |
378 | h.Write(docBytes)
379 | d := h.Sum(nil)
380 | calculatedValue := base64.StdEncoding.EncodeToString(d)
381 |
382 | return calculatedValue, nil
383 | }
384 |
385 | // removeXMLDeclaration searches for and removes the XML declaration processing instruction.
386 | func removeXMLDeclaration(doc *etree.Document) {
387 | for _, t := range doc.Child {
388 | if p, ok := t.(*etree.ProcInst); ok && p.Target == "xml" {
389 | doc.RemoveChild(p)
390 | break
391 | }
392 | }
393 |
394 | // Remove CharData right after removing the XML declaration
395 | for _, t2 := range doc.Child {
396 | if p2, ok := t2.(*etree.CharData); ok {
397 | doc.RemoveChild(p2)
398 | }
399 | }
400 | }
401 |
402 | // parseXML parses an XML string into an etree.Document and removes the XML declaration if present.
403 | func parseXML(xml string) (*etree.Document, error) {
404 | doc := etree.NewDocument()
405 | if err := doc.ReadFromString(xml); err != nil {
406 | return nil, err
407 | }
408 |
409 | doc.ReadSettings.PreserveCData = true
410 | removeXMLDeclaration(doc)
411 |
412 | return doc, nil
413 | }
414 |
--------------------------------------------------------------------------------
/testdata/invalid-signature-signature-value.xml:
--------------------------------------------------------------------------------
1 | qIVhfzD3HVMA4BUQZ+zUF6AlFgcL7FyQ8tN35NZWFJs=c2lnbmVkeG1sOg==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NameThe mutable display name of the user.SubjectAn immutable, globally unique, non-reusable identifier of the user that is unique to the application for which a token is issued.Given NameFirst name of the user.SurnameLast name of the user.Display NameDisplay name of the user.Nick NameNick name of the user.Authentication InstantThe time (UTC) when the user is authenticated to Windows Azure Active Directory.Authentication MethodThe method that Windows Azure Active Directory uses to authenticate users.ObjectIdentifierPrimary identifier for the user in the directory. Immutable, globally unique, non-reusable.TenantIdIdentifier for the user's tenant.IdentityProviderIdentity provider for the user.EmailEmail address of the user.GroupsGroups of the user.External Access TokenAccess token issued by external identity provider.External Access Token ExpirationUTC expiration time of access token issued by external identity provider.External OpenID 2.0 IdentifierOpenID 2.0 identifier issued by external identity provider.GroupsOverageClaimIssued when number of user's group claims exceeds return limit.Role ClaimRoles that the user or Service Principal is attached toRoleTemplate Id ClaimRole template id of the Built-in Directory Roles that the user is a member ofhttps://login.windows.net/8bd6e98d-e212-4022-b13f-a244fab4c253/wsfedhttps://login.windows.net/8bd6e98d-e212-4022-b13f-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https://sts.windows.net/8bd6e98d-e212-4022-b13f-a244fab4c253/https://login.windows.net/8bd6e98d-e212-4022-b13f-a244fab4c253/wsfedhttps://login.windows.net/8bd6e98d-e212-4022-b13f-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
2 |
--------------------------------------------------------------------------------
/testdata/wsfed-metadata.xml:
--------------------------------------------------------------------------------
1 | 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NameThe mutable display name of the user.SubjectAn immutable, globally unique, non-reusable identifier of the user that is unique to the application for which a token is issued.Given NameFirst name of the user.SurnameLast name of the user.Display NameDisplay name of the user.Nick NameNick name of the user.Authentication InstantThe time (UTC) when the user is authenticated to Windows Azure Active Directory.Authentication MethodThe method that Windows Azure Active Directory uses to authenticate users.ObjectIdentifierPrimary identifier for the user in the directory. Immutable, globally unique, non-reusable.TenantIdIdentifier for the user's tenant.IdentityProviderIdentity provider for the user.EmailEmail address of the user.GroupsGroups of the user.External Access TokenAccess token issued by external identity provider.External Access Token ExpirationUTC expiration time of access token issued by external identity provider.External OpenID 2.0 IdentifierOpenID 2.0 identifier issued by external identity provider.GroupsOverageClaimIssued when number of user's group claims exceeds return limit.Role ClaimRoles that the user or Service Principal is attached toRoleTemplate Id ClaimRole template id of the Built-in Directory Roles that the user is a member ofhttps://login.windows.net/8bd6e98d-e212-4022-b13f-a244fab4c253/wsfedhttps://login.windows.net/8bd6e98d-e212-4022-b13f-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https://sts.windows.net/8bd6e98d-e212-4022-b13f-a244fab4c253/https://login.windows.net/8bd6e98d-e212-4022-b13f-a244fab4c253/wsfedhttps://login.windows.net/8bd6e98d-e212-4022-b13f-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
--------------------------------------------------------------------------------
/testdata/invalid-signature-changed-content.xml:
--------------------------------------------------------------------------------
1 | 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NameThe mutable display name of the user.SubjectAn immutable, globally unique, non-reusable identifier of the user that is unique to the application for which a token is issued.Given NameFirst name of the user.SurnameLast name of the user.Display NameDisplay name of the user.Nick NameNick name of the user.Authentication InstantThe time (UTC) when the user is authenticated to Windows Azure Active Directory.Authentication MethodThe method that Windows Azure Active Directory uses to authenticate users.ObjectIdentifierPrimary identifier for the user in the directory. Immutable, globally unique, non-reusable.TenantIdIdentifier for the user's tenant.IdentityProviderIdentity provider for the user.EmailEmail address of the user.GroupsGroups of the user.External Access TokenAccess token issued by external identity provider.External Access Token ExpirationUTC expiration time of access token issued by external identity provider.External OpenID 2.0 IdentifierOpenID 2.0 identifier issued by external identity provider.GroupsOverageClaimIssued when number of user's group claims exceeds return limit.Role ClaimRoles that the user or Service Principal is attached toRoleTemplate Id ClaimRole template id of the Built-in Directory Roles that the user is a member ofhttps://login.windows.net/8bd6e98d-e212-4022-b13f-a244fab4c253/wsfedhttps://login.windows.net/8bd6e98d-e212-4022-b13f-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https://sts.windows.net/8bd6e98d-e212-4022-b13f-a244fab4c253/https://login.windows.net/8bd6e98d-e212-4022-b13f-a244fab4c253/wsfedhttps://login.windows.net/8bd6e98d-e212-4022-b13f-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
2 |
--------------------------------------------------------------------------------
/testdata/invalid-signature-non-existing-reference.xml:
--------------------------------------------------------------------------------
1 | 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NameThe mutable display name of the user.SubjectAn immutable, globally unique, non-reusable identifier of the user that is unique to the application for which a token is issued.Given NameFirst name of the user.SurnameLast name of the user.Display NameDisplay name of the user.Nick NameNick name of the user.Authentication InstantThe time (UTC) when the user is authenticated to Windows Azure Active Directory.Authentication MethodThe method that Windows Azure Active Directory uses to authenticate users.ObjectIdentifierPrimary identifier for the user in the directory. Immutable, globally unique, non-reusable.TenantIdIdentifier for the user's tenant.IdentityProviderIdentity provider for the user.EmailEmail address of the user.GroupsGroups of the user.External Access TokenAccess token issued by external identity provider.External Access Token ExpirationUTC expiration time of access token issued by external identity provider.External OpenID 2.0 IdentifierOpenID 2.0 identifier issued by external identity provider.GroupsOverageClaimIssued when number of user's group claims exceeds return limit.Role ClaimRoles that the user or Service Principal is attached toRoleTemplate Id ClaimRole template id of the Built-in Directory Roles that the user is a member ofhttps://login.windows.net/8bd6e98d-e212-4022-b13f-a244fab4c253/wsfedhttps://login.windows.net/8bd6e98d-e212-4022-b13f-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https://sts.windows.net/8bd6e98d-e212-4022-b13f-a244fab4c253/https://login.windows.net/8bd6e98d-e212-4022-b13f-a244fab4c253/wsfedhttps://login.windows.net/8bd6e98d-e212-4022-b13f-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
2 |
--------------------------------------------------------------------------------
/signedxml_test.go:
--------------------------------------------------------------------------------
1 | package signedxml
2 |
3 | import (
4 | "crypto/x509"
5 | "encoding/base64"
6 | "encoding/pem"
7 | "fmt"
8 | "io"
9 | "os"
10 | "path/filepath"
11 | "testing"
12 |
13 | "github.com/beevik/etree"
14 | . "github.com/smartystreets/goconvey/convey"
15 | )
16 |
17 | func TestSignatureLogicWithCLibXMLSec(t *testing.T) {
18 | pemString, _ := os.ReadFile("./testdata/rsa.crt")
19 | pemBlock, _ := pem.Decode([]byte(pemString))
20 | cert, _ := x509.ParseCertificate(pemBlock.Bytes)
21 |
22 | b64Bytes, _ := os.ReadFile("./testdata/rsa.key.b64")
23 | pemString, _ = base64.StdEncoding.DecodeString(string(b64Bytes))
24 | pemBlock, _ = pem.Decode([]byte(pemString))
25 | key, _ := x509.ParsePKCS1PrivateKey(pemBlock.Bytes)
26 |
27 | xmlGeneratedSign, _ := os.ReadFile("./testdata/signature-generate-using-xmlseclib.xml")
28 | signedDoc := etree.NewDocument()
29 | signedDoc.ReadFromString(string(xmlGeneratedSign))
30 | signature := signedDoc.Root()
31 | digestValueElement := signature.SelectElement("Signature").SelectElement("SignedInfo").SelectElement("Reference").SelectElement("DigestValue")
32 |
33 | Convey("Given an XML, certificate, and RSA key", t, func() {
34 | xml, _ := os.ReadFile("./testdata/doc.xml")
35 |
36 | Convey("When generating the signature", func() {
37 | signer, _ := NewSigner(string(xml))
38 | xmlStr, err := signer.Sign(key)
39 | generatedDigestValue := signer.signedInfo.SelectElement("Reference").SelectElement("DigestValue")
40 | Convey("Then no error occurs", func() {
41 | So(err, ShouldBeNil)
42 | })
43 | Convey("And the signature should be valid", func() {
44 | validator, _ := NewValidator(xmlStr)
45 | validator.Certificates = append(validator.Certificates, *cert)
46 | refs, err := validator.ValidateReferences()
47 | So(err, ShouldBeNil)
48 | So(len(refs), ShouldEqual, 1)
49 | })
50 | Convey("And signature digest should match with signature generated using xmlsec", func() {
51 | So(generatedDigestValue.Text(), ShouldEqual, digestValueElement.Text())
52 | })
53 | Convey("And signature generated using xmlsec should be valid", func() {
54 | validator, _ := NewValidator(string(xmlGeneratedSign))
55 | validator.Certificates = append(validator.Certificates, *cert)
56 | refs, err := validator.ValidateReferences()
57 | So(err, ShouldBeNil)
58 | So(len(refs), ShouldEqual, 1)
59 | })
60 | })
61 | })
62 | }
63 |
64 | func TestSign(t *testing.T) {
65 | pemString, _ := os.ReadFile("./testdata/rsa.crt")
66 | pemBlock, _ := pem.Decode([]byte(pemString))
67 | cert, _ := x509.ParseCertificate(pemBlock.Bytes)
68 |
69 | b64Bytes, _ := os.ReadFile("./testdata/rsa.key.b64")
70 | pemString, _ = base64.StdEncoding.DecodeString(string(b64Bytes))
71 | pemBlock, _ = pem.Decode([]byte(pemString))
72 | key, _ := x509.ParsePKCS1PrivateKey(pemBlock.Bytes)
73 |
74 | Convey("Given an XML, certificate, and RSA key", t, func() {
75 | xml, _ := os.ReadFile("./testdata/nosignature.xml")
76 |
77 | Convey("When generating the signature", func() {
78 | signer, _ := NewSigner(string(xml))
79 | xmlStr, err := signer.Sign(key)
80 | Convey("Then no error occurs", func() {
81 | So(err, ShouldBeNil)
82 | })
83 | Convey("And the signature should be valid", func() {
84 | validator, _ := NewValidator(xmlStr)
85 | validator.Certificates = append(validator.Certificates, *cert)
86 | refs, err := validator.ValidateReferences()
87 | So(err, ShouldBeNil)
88 | So(len(refs), ShouldEqual, 1)
89 | })
90 | })
91 | })
92 |
93 | Convey("Given an XML with http://www.w3.org/TR/2001/REC-xml-c14n-20010315 canonicalization, certificate, and RSA key", t, func() {
94 | xml, _ := os.ReadFile("./testdata/nosignature2.xml")
95 |
96 | Convey("When generating the signature", func() {
97 | signer, _ := NewSigner(string(xml))
98 | xmlStr, err := signer.Sign(key)
99 | Convey("Then no error occurs", func() {
100 | So(err, ShouldBeNil)
101 | })
102 | Convey("And the signature should be valid", func() {
103 | validator, _ := NewValidator(xmlStr)
104 | validator.Certificates = append(validator.Certificates, *cert)
105 | refs, err := validator.ValidateReferences()
106 | So(err, ShouldBeNil)
107 | So(len(refs), ShouldEqual, 1)
108 | })
109 | })
110 | })
111 |
112 | Convey("Given an XML with custom referenceIDAttribute, certificate, and RSA key", t, func() {
113 | xml, _ := os.ReadFile("./testdata/nosignature-custom-reference-id-attribute.xml")
114 |
115 | Convey("When generating the signature with custom referenceIDAttribute", func() {
116 | signer, _ := NewSigner(string(xml))
117 | signer.SetReferenceIDAttribute("customId")
118 | xmlStr, err := signer.Sign(key)
119 | Convey("Then no error occurs", func() {
120 | So(err, ShouldBeNil)
121 | })
122 | Convey("And the signature should be valid", func() {
123 | validator, _ := NewValidator(xmlStr)
124 | validator.Certificates = append(validator.Certificates, *cert)
125 | validator.SetReferenceIDAttribute("customId")
126 | refs, err := validator.ValidateReferences()
127 | So(err, ShouldBeNil)
128 | So(len(refs), ShouldEqual, 1)
129 | })
130 | Convey("And the signature should be valid, but validation fail if referenceIDAttribute NOT SET", func() {
131 | validator, err := NewValidator(xmlStr)
132 | So(err, ShouldBeNil)
133 | So(validator, ShouldNotBeNil)
134 | validator.Certificates = append(validator.Certificates, *cert)
135 | refs, err := validator.ValidateReferences()
136 | So(err, ShouldNotBeNil)
137 | So(len(refs), ShouldEqual, 0)
138 | })
139 | })
140 |
141 | Convey("When generating the signature referenceIDAttribute NOT SET", func() {
142 | signer, _ := NewSigner(string(xml))
143 | _, err := signer.Sign(key)
144 | Convey("Then an error should occur", func() {
145 | So(err, ShouldNotBeNil)
146 | })
147 | })
148 |
149 | })
150 |
151 | Convey("Signature at the Root level, surrounding the Object", t, func() {
152 | xml, _ := os.ReadFile(filepath.Join("testdata", "root-level-signature.xml"))
153 |
154 | doc := etree.NewDocument()
155 | doc.ReadFromBytes(xml)
156 | signature := doc.FindElement("//Signature")
157 | t.Logf("signature: %#v", signature)
158 |
159 | signer, _ := NewSigner(string(xml))
160 | signer.SetReferenceIDAttribute("Id")
161 | xmlStr, err := signer.Sign(key)
162 | t.Logf("%#v", xmlStr)
163 | So(err, ShouldBeNil)
164 | So(xmlStr, ShouldNotBeNil)
165 |
166 | validator, err := NewValidator(xmlStr)
167 | So(err, ShouldBeNil)
168 | validator.SetReferenceIDAttribute("Id")
169 | validator.Certificates = append(validator.Certificates, *cert)
170 | refs, err := validator.ValidateReferences()
171 | So(err, ShouldBeNil)
172 | So(len(refs), ShouldEqual, 1)
173 | })
174 | }
175 |
176 | func TestValidate(t *testing.T) {
177 | Convey("Given valid signed XML", t, func() {
178 | cases := map[string]string{
179 | "(WSFed BBAuth Metadata)": "./testdata/bbauth-metadata.xml",
180 | "(SAML External NS)": "./testdata/saml-external-ns.xml",
181 | "(Signature w/Inclusive NS)": "./testdata/signature-with-inclusivenamespaces.xml",
182 | "(SAML)": "./testdata/valid-saml.xml",
183 | // this one doesn't appear to follow the spec... ( http://webservices20.blogspot.co.il/2013/06/validating-windows-mobile-app-store.html)
184 | //"(Windows Store Signature)": "./testdata/windows-store-signature.xml",
185 | "(WSFed Generic Metadata)": "./testdata/wsfed-metadata.xml",
186 | }
187 |
188 | for description, test := range cases {
189 | Convey("When Validate is called "+description, func() {
190 | xmlFile, err := os.Open(test)
191 | if err != nil {
192 | fmt.Println("Error opening file:", err)
193 | }
194 | defer xmlFile.Close()
195 | xmlBytes, _ := io.ReadAll(xmlFile)
196 | validator, _ := NewValidator(string(xmlBytes))
197 | refs, err := validator.ValidateReferences()
198 | Convey("Then no error occurs", func() {
199 | So(err, ShouldBeNil)
200 | So(validator.SigningCert().PublicKey, ShouldNotBeNil)
201 | So(len(refs), ShouldEqual, 1)
202 | })
203 | })
204 | }
205 |
206 | Convey("When Validate is called with an external Signature", func() {
207 | xmlFile, _ := os.Open("./testdata/bbauth-metadata.xml")
208 | sig := `LPHoiAkLmA/TGIuVbgpwlFLXL+ymEBc7TS0fC9/PTQU=d2CXq9GEeDKvMxdpxtTRKQ8TGeSWhJOVPs8LMD0ObeE1t/YGiAm9keorMiki4laxbWqAuOmwHK3qNHogRFgkIYi3fnuFBzMrahXf0n3A5PRXXW1m768Z92GKV09pGuygKUXCtXzwq0seDi6PnzMCJFzFXGQWnum0paa8Oz+6425Sn0zO0fT3ttp3AXeXGyNXwYPYcX1iEMB7klUlyiz2hmn8ngCIbTkru7uIeyPmQ5WD4SS/qQaL4yb3FZibXoe/eRXrbkG1NAJCw9OWw0jsvWncE1rKFaqEMbz21fXSDhh3Ls+p9yVf+xbCrpkT0FMqjTHpNRvccMPZe/hDGrHV7Q==MIIDNzCCAh+gAwIBAgIQQVK+d/vLK4ZNMDk15HGUoTANBgkqhkiG9w0BAQ0FADAoMSYwJAYDVQQDEx1CbGFja2JhdWQgQXV0aGVudGljYXRpb24gMjAyMjAeFw0wMDAxMDEwNDAwMDBaFw0yMjAxMDEwNDAwMDBaMCgxJjAkBgNVBAMTHUJsYWNrYmF1ZCBBdXRoZW50aWNhdGlvbiAyMDIyMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEArgByjSPVvP4DLf/l7QRz7G7Dhkdns0QjWslnWejHlFIezfkJ4NGPp0+5CRCFYBqAb7DhqyK77Ek5xdzmwgYb1X6GD6UDltWvN5BBFAw69I6/K0WjguFUxk19T7xdc8vTCNAMi+6Ys49O3EBNnI2fiqDoBdMjUTud1F04QY3N2rZWkjMrHV+CnzhoUwqsO/ABWrDbkPzBXdOOIbsKH0k0IP8q2+35pe1y2nxtB9f1fCyCmbUH2HINMHahDmxxanTW5Jy14yD/HSRTFQF9JMTeglomWq5q9VPx0NjsEJR+B5IkRCTf75LoYrrr/fvQm3aummmYPdHauXCBrcm0moX4ywIDAQABo10wWzBZBgNVHQEEUjBQgBDCHOfardZfhltQSbLqsukZoSowKDEmMCQGA1UEAxMdQmxhY2tiYXVkIEF1dGhlbnRpY2F0aW9uIDIwMjKCEEFSvnf7yyuGTTA5NeRxlKEwDQYJKoZIhvcNAQENBQADggEBADrOhfRiynRKGD7EHohpPrltFScJ9+QErYMhEvteqh3C48T99uKgDY8wTqv+PI08QUSZuhmmF2d+W7aRBo3t8ZZepIXCwDaKo/oUp2h5Y9O3vyGDguq5ptgDTmPNYDCwWtdt0TtQYeLtCQTJVbYByWL0eT+KdzQOkAi48cPEOObSc9Biga7LTCcbCVPeJlYzmHDQUhzBt2jcy5BGvmZloI5SsoZvve6ug74qNq8IJMyzJzUp3kRuB0ruKIioSDi1lc783LDT3LSXyIbOGw/vHBEBY4Ax7FK8CqXJ2TsYqVsyo8QypqXDnveLcgK+PNEAhezhxC9hyV8j1I8pfF72ABE=`
209 | defer xmlFile.Close()
210 | xmlBytes, _ := io.ReadAll(xmlFile)
211 | validator := Validator{}
212 | validator.SetXML(string(xmlBytes))
213 | validator.SetSignature(sig)
214 | refs, err := validator.ValidateReferences()
215 | Convey("Then no error occurs", func() {
216 | So(err, ShouldBeNil)
217 | So(validator.SigningCert().PublicKey, ShouldNotBeNil)
218 | So(len(refs), ShouldEqual, 1)
219 | })
220 | })
221 |
222 | Convey("When Validate is called with an external certificate and root xmlns", func() {
223 | xmlFile, _ := os.Open("./testdata/rootxmlns.xml")
224 | pemString, _ := os.ReadFile("./testdata/rootxmlns.crt")
225 | pemBlock, _ := pem.Decode([]byte(pemString))
226 | cert, _ := x509.ParseCertificate(pemBlock.Bytes)
227 | defer xmlFile.Close()
228 | xmlBytes, _ := io.ReadAll(xmlFile)
229 | validator := Validator{}
230 | validator.SetXML(string(xmlBytes))
231 | validator.Certificates = append(validator.Certificates, *cert)
232 | refs, err := validator.ValidateReferences()
233 | Convey("Then no error occurs", func() {
234 | So(err, ShouldBeNil)
235 | So(validator.SigningCert().PublicKey, ShouldNotBeNil)
236 | So(len(refs), ShouldEqual, 1)
237 | })
238 | })
239 | })
240 |
241 | Convey("Given invalid signed XML", t, func() {
242 | cases := map[string]string{
243 | "(Changed Content)": "./testdata/invalid-signature-changed-content.xml",
244 | "(Non-existing Reference)": "./testdata/invalid-signature-non-existing-reference.xml",
245 | }
246 | for description, test := range cases {
247 | Convey("When ValidateReferences is called "+description, func() {
248 | xmlBytes, err := os.ReadFile(test)
249 | if err != nil {
250 | fmt.Println("Error reading file:", err)
251 | }
252 | validator, _ := NewValidator(string(xmlBytes))
253 |
254 | refs, err := validator.ValidateReferences()
255 | Convey("Then an error occurs", func() {
256 | So(err, ShouldNotBeNil)
257 | So(err.Error(), ShouldContainSubstring, "signedxml")
258 | t.Logf("%v - %d", description, len(refs))
259 | So(len(refs), ShouldEqual, 0)
260 | })
261 | })
262 | }
263 |
264 | cases = map[string]string{
265 | "(Wrong Sig Value)": "./testdata/invalid-signature-signature-value.xml",
266 | }
267 | for description, test := range cases {
268 | Convey("When ValidateReferences is called "+description, func() {
269 | xmlBytes, err := os.ReadFile(test)
270 | if err != nil {
271 | fmt.Println("Error reading file:", err)
272 | }
273 | validator, _ := NewValidator(string(xmlBytes))
274 |
275 | refs, err := validator.ValidateReferences()
276 | Convey("Then an error occurs", func() {
277 | So(err, ShouldNotBeNil)
278 | So(err.Error(), ShouldContainSubstring, "signedxml:")
279 | t.Logf("%v - %d", description, len(refs))
280 | So(len(refs), ShouldEqual, 1)
281 | })
282 | })
283 | }
284 | })
285 | }
286 |
287 | func TestEnvelopedSignatureProcess(t *testing.T) {
288 | Convey("Given a document without a Signature elemement", t, func() {
289 | doc := ""
290 | Convey("When ProcessDocument is called", func() {
291 | envSig := EnvelopedSignature{}
292 | _, err := envSig.Process(doc, "")
293 | Convey("Then an error occurs", func() {
294 | So(err, ShouldNotBeNil)
295 | So(err.Error(), ShouldContainSubstring, "signedxml:")
296 | })
297 | })
298 | })
299 | }
300 |
301 | func TestSignatureDataParsing(t *testing.T) {
302 | Convey("Given a document without a Signature elemement", t, func() {
303 | doc := etree.NewDocument()
304 | doc.CreateElement("root")
305 | Convey("When parseEnvelopedSignature is called", func() {
306 | sigData := signatureData{xml: doc}
307 | err := sigData.parseEnvelopedSignature()
308 | Convey("Then an error occurs", func() {
309 | So(err, ShouldNotBeNil)
310 | So(err.Error(), ShouldContainSubstring, "signedxml:")
311 | })
312 | })
313 | })
314 |
315 | Convey("Given a document without a SignedInfo elemement", t, func() {
316 | doc := etree.NewDocument()
317 | doc.CreateElement("root").CreateElement("Signature")
318 | sigData := signatureData{xml: doc}
319 | sigData.parseEnvelopedSignature()
320 | Convey("When parseSignedInfo is called", func() {
321 | err := sigData.parseSignedInfo()
322 | Convey("Then an error occurs", func() {
323 | So(err, ShouldNotBeNil)
324 | So(err.Error(), ShouldContainSubstring, "signedxml:")
325 | })
326 | })
327 | })
328 |
329 | Convey("Given a document without a SignatureValue elemement", t, func() {
330 | doc := etree.NewDocument()
331 | doc.CreateElement("root").CreateElement("Signature")
332 | sigData := signatureData{xml: doc}
333 | sigData.parseEnvelopedSignature()
334 | Convey("When parseSigValue is called", func() {
335 | err := sigData.parseSigValue()
336 | Convey("Then an error occurs", func() {
337 | So(err, ShouldNotBeNil)
338 | So(err.Error(), ShouldContainSubstring, "signedxml:")
339 | })
340 | })
341 | })
342 |
343 | Convey("Given a document without a SignatureMethod elemement", t, func() {
344 | doc := etree.NewDocument()
345 | doc.CreateElement("root").CreateElement("Signature").CreateElement("SignedInfo")
346 | sigData := signatureData{xml: doc}
347 | sigData.parseEnvelopedSignature()
348 | sigData.parseSignedInfo()
349 | Convey("When parseSigAlgorithm is called", func() {
350 | err := sigData.parseSigAlgorithm()
351 | Convey("Then an error occurs", func() {
352 | So(err, ShouldNotBeNil)
353 | So(err.Error(), ShouldContainSubstring, "signedxml:")
354 | })
355 | })
356 | })
357 |
358 | Convey("Given a document without a SignatureMethod Algorithm element", t, func() {
359 | doc := etree.NewDocument()
360 | doc.CreateElement("root").CreateElement("Signature").CreateElement("SignedInfo").CreateElement("SignatureMethod")
361 | sigData := signatureData{xml: doc}
362 | sigData.parseEnvelopedSignature()
363 | sigData.parseSignedInfo()
364 | Convey("When parseSigAlgorithm is called", func() {
365 | err := sigData.parseSigAlgorithm()
366 | Convey("Then an error occurs", func() {
367 | So(err, ShouldNotBeNil)
368 | So(err.Error(), ShouldContainSubstring, "signedxml:")
369 | })
370 | })
371 | })
372 |
373 | Convey("Given a document without a CanonicalizationMethod elemement", t, func() {
374 | doc := etree.NewDocument()
375 | doc.CreateElement("root").CreateElement("Signature").CreateElement("SignedInfo")
376 | sigData := signatureData{xml: doc}
377 | sigData.parseEnvelopedSignature()
378 | sigData.parseSignedInfo()
379 | Convey("When parseCanonAlgorithm is called", func() {
380 | err := sigData.parseCanonAlgorithm()
381 | Convey("Then an error occurs", func() {
382 | So(err, ShouldNotBeNil)
383 | So(err.Error(), ShouldContainSubstring, "signedxml:")
384 | })
385 | })
386 | })
387 |
388 | Convey("Given a document without a CanonicalizationMethod Algorithm element", t, func() {
389 | doc := etree.NewDocument()
390 | doc.CreateElement("root").CreateElement("Signature").CreateElement("SignedInfo").CreateElement("CanonicalizationMethod")
391 | sigData := signatureData{xml: doc}
392 | sigData.parseEnvelopedSignature()
393 | sigData.parseSignedInfo()
394 | Convey("When parseCanonAlgorithm is called", func() {
395 | err := sigData.parseCanonAlgorithm()
396 | Convey("Then an error occurs", func() {
397 | So(err, ShouldNotBeNil)
398 | So(err.Error(), ShouldContainSubstring, "signedxml:")
399 | })
400 | })
401 | })
402 | }
403 |
--------------------------------------------------------------------------------