├── 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 | 21 | name 22 | 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 | 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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 | [![Moov Banner Logo](https://user-images.githubusercontent.com/20115216/104214617-885b3c80-53ec-11eb-8ce0-9fc745fb5bfc.png)](https://github.com/moov-io) 2 | 3 | ## moov-io/signedxml 4 | 5 | [![GoDoc](https://godoc.org/github.com/moov-io/signedxml?status.svg)](https://godoc.org/github.com/moov-io/signedxml) 6 | [![Build Status](https://github.com/moov-io/signedxml/workflows/Go/badge.svg)](https://github.com/moov-io/signedxml/actions) 7 | [![Coverage Status](https://codecov.io/gh/moov-io/signedxml/branch/master/graph/badge.svg)](https://codecov.io/gh/moov-io/signedxml) 8 | [![Go Report Card](https://goreportcard.com/badge/github.com/moov-io/signedxml)](https://goreportcard.com/report/github.com/moov-io/signedxml) 9 | [![Repo Size](https://img.shields.io/github/languages/code-size/moov-io/signedxml?label=project%20size)](https://github.com/moov-io/signedxml) 10 | [![MIT License](https://img.shields.io/badge/license-MIT-blue.svg)](https://raw.githubusercontent.com/moov-io/signedxml/master/LICENSE.md) 11 | [![Slack Channel](https://slack.moov.io/badge.svg?bg=e01563&fgColor=fffff)](https://slack.moov.io/) 12 | [![Twitter](https://img.shields.io/twitter/follow/moov?style=social)](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==MIIC4jCCAcqgAwIBAgIQQNXrmzhLN4VGlUXDYCRT3zANBgkqhkiG9w0BAQsFADAtMSswKQYDVQQDEyJhY2NvdW50cy5hY2Nlc3Njb250cm9sLndpbmRvd3MubmV0MB4XDTE0MTAyODAwMDAwMFoXDTE2MTAyNzAwMDAwMFowLTErMCkGA1UEAxMiYWNjb3VudHMuYWNjZXNzY29udHJvbC53aW5kb3dzLm5ldDCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBALyKs/uPhEf7zVizjfcr/ISGFe9+yUOqwpel38zgutvLHmFD39E2hpPdQhcXn4c4dt1fU5KvkbcDdVbP8+e4TvNpJMy/nEB2V92zCQ/hhBjilwhF1ETe1TMmVjALs0KFvbxW9ZN3EdUVvxFvz/gvG29nQhl4QWKj3x8opr89lmq14Z7T0mzOV8kub+cgsOU/1bsKqrIqN1fMKKFhjKaetctdjYTfGzVQ0AJAzzbtg0/Q1wdYNAnhSDafygEv6kNiquk0r0RyasUUevEXs2LY3vSgKsKseI8ZZlQEMtE9/k/iAG7JNcEbVg53YTurNTrPnXJOU88mf3TToX14HpYsS1ECAwEAATANBgkqhkiG9w0BAQsFAAOCAQEAfolx45w0i8CdAUjjeAaYdhG9+NDHxop0UvNOqlGqYJexqPLuvX8iyUaYxNGzZxFgGI3GpKfmQP2JQWQ1E5JtY/n8iNLOKRMwqkuxSCKJxZJq4Sl/m/Yv7TS1P5LNgAj8QLCypxsWrTAmq2HSpkeSk4JBtsYxX6uhbGM/K1sEktKybVTHu22/7TmRqWTmOUy9wQvMjJb2IXdMGLG3hVntN/WWcs5w8vbt1i8Kk6o19W2MjZ95JaECKjBDYRlhG1KmSBtrsKsCBQoBzwH/rXfksTO9JoUYLXiW0IppB7DhNH4PJ5hZI91R8rR0H3/bKkLSuDaKLWSqMhozdhXsIIKvJQ==MIIDPjCCAiqgAwIBAgIQsRiM0jheFZhKk49YD0SK1TAJBgUrDgMCHQUAMC0xKzApBgNVBAMTImFjY291bnRzLmFjY2Vzc2NvbnRyb2wud2luZG93cy5uZXQwHhcNMTQwMTAxMDcwMDAwWhcNMTYwMTAxMDcwMDAwWjAtMSswKQYDVQQDEyJhY2NvdW50cy5hY2Nlc3Njb250cm9sLndpbmRvd3MubmV0MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAkSCWg6q9iYxvJE2NIhSyOiKvqoWCO2GFipgH0sTSAs5FalHQosk9ZNTztX0ywS/AHsBeQPqYygfYVJL6/EgzVuwRk5txr9e3n1uml94fLyq/AXbwo9yAduf4dCHTP8CWR1dnDR+Qnz/4PYlWVEuuHHONOw/blbfdMjhY+C/BYM2E3pRxbohBb3x//CfueV7ddz2LYiH3wjz0QS/7kjPiNCsXcNyKQEOTkbHFi3mu0u13SQwNddhcynd/GTgWN8A+6SN1r4hzpjFKFLbZnBt77ACSiYx+IHK4Mp+NaVEi5wQtSsjQtI++XsokxRDqYLwus1I1SihgbV/STTg5enufuwIDAQABo2IwYDBeBgNVHQEEVzBVgBDLebM6bK3BjWGqIBrBNFeNoS8wLTErMCkGA1UEAxMiYWNjb3VudHMuYWNjZXNzY29udHJvbC53aW5kb3dzLm5ldIIQsRiM0jheFZhKk49YD0SK1TAJBgUrDgMCHQUAA4IBAQCJ4JApryF77EKC4zF5bUaBLQHQ1PNtA1uMDbdNVGKCmSf8M65b8h0NwlIjGGGy/unK8P6jWFdm5IlZ0YPTOgzcRZguXDPj7ajyvlVEQ2K2ICvTYiRQqrOhEhZMSSZsTKXFVwNfW6ADDkN3bvVOVbtpty+nBY5UqnI7xbcoHLZ4wYD251uj5+lo13YLnsVrmQ16NCBYq2nQFNPuNJw6t3XUbwBHXpF46aLT1/eGf/7Xx6iy8yPJX4DyrpFTutDz882RWofGEO5t4Cw+zZg70dJ/hH/ODYRMorfXEW+8uKmXMKmX2wyxMKvfiPbTy5LmAU8Jvjs2tLg4rOBcXWLAIarZMIIC4jCCAcqgAwIBAgIQQNXrmzhLN4VGlUXDYCRT3zANBgkqhkiG9w0BAQsFADAtMSswKQYDVQQDEyJhY2NvdW50cy5hY2Nlc3Njb250cm9sLndpbmRvd3MubmV0MB4XDTE0MTAyODAwMDAwMFoXDTE2MTAyNzAwMDAwMFowLTErMCkGA1UEAxMiYWNjb3VudHMuYWNjZXNzY29udHJvbC53aW5kb3dzLm5ldDCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBALyKs/uPhEf7zVizjfcr/ISGFe9+yUOqwpel38zgutvLHmFD39E2hpPdQhcXn4c4dt1fU5KvkbcDdVbP8+e4TvNpJMy/nEB2V92zCQ/hhBjilwhF1ETe1TMmVjALs0KFvbxW9ZN3EdUVvxFvz/gvG29nQhl4QWKj3x8opr89lmq14Z7T0mzOV8kub+cgsOU/1bsKqrIqN1fMKKFhjKaetctdjYTfGzVQ0AJAzzbtg0/Q1wdYNAnhSDafygEv6kNiquk0r0RyasUUevEXs2LY3vSgKsKseI8ZZlQEMtE9/k/iAG7JNcEbVg53YTurNTrPnXJOU88mf3TToX14HpYsS1ECAwEAATANBgkqhkiG9w0BAQsFAAOCAQEAfolx45w0i8CdAUjjeAaYdhG9+NDHxop0UvNOqlGqYJexqPLuvX8iyUaYxNGzZxFgGI3GpKfmQP2JQWQ1E5JtY/n8iNLOKRMwqkuxSCKJxZJq4Sl/m/Yv7TS1P5LNgAj8QLCypxsWrTAmq2HSpkeSk4JBtsYxX6uhbGM/K1sEktKybVTHu22/7TmRqWTmOUy9wQvMjJb2IXdMGLG3hVntN/WWcs5w8vbt1i8Kk6o19W2MjZ95JaECKjBDYRlhG1KmSBtrsKsCBQoBzwH/rXfksTO9JoUYLXiW0IppB7DhNH4PJ5hZI91R8rR0H3/bKkLSuDaKLWSqMhozdhXsIIKvJQ==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 of
https://login.windows.net/8bd6e98d-e212-4022-b13f-a244fab4c253/wsfed
https://login.windows.net/8bd6e98d-e212-4022-b13f-a244fab4c253/wsfed
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
https://sts.windows.net/8bd6e98d-e212-4022-b13f-a244fab4c253/
https://login.windows.net/8bd6e98d-e212-4022-b13f-a244fab4c253/wsfed
https://login.windows.net/8bd6e98d-e212-4022-b13f-a244fab4c253/wsfed
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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 of
https://login.windows.net/8bd6e98d-e212-4022-b13f-a244fab4c253/wsfed
https://login.windows.net/8bd6e98d-e212-4022-b13f-a244fab4c253/wsfed
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
https://sts.windows.net/8bd6e98d-e212-4022-b13f-a244fab4c253/
https://login.windows.net/8bd6e98d-e212-4022-b13f-a244fab4c253/wsfed
https://login.windows.net/8bd6e98d-e212-4022-b13f-a244fab4c253/wsfed
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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 of
https://login.windows.net/8bd6e98d-e212-4022-b13f-a244fab4c253/wsfed
https://login.windows.net/8bd6e98d-e212-4022-b13f-a244fab4c253/wsfed
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
https://sts.windows.net/8bd6e98d-e212-4022-b13f-a244fab4c253/
https://login.windows.net/8bd6e98d-e212-4022-b13f-a244fab4c253/wsfed
https://login.windows.net/8bd6e98d-e212-4022-b13f-a244fab4c253/wsfed
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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 of
https://login.windows.net/8bd6e98d-e212-4022-b13f-a244fab4c253/wsfed
https://login.windows.net/8bd6e98d-e212-4022-b13f-a244fab4c253/wsfed
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
https://sts.windows.net/8bd6e98d-e212-4022-b13f-a244fab4c253/
https://login.windows.net/8bd6e98d-e212-4022-b13f-a244fab4c253/wsfed
https://login.windows.net/8bd6e98d-e212-4022-b13f-a244fab4c253/wsfed
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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 | --------------------------------------------------------------------------------