diff --git a/.gitignore b/.gitignore
old mode 100644
new mode 100755
index daf913b..a5e5c2a
--- a/.gitignore
+++ b/.gitignore
@@ -22,3 +22,5 @@ _testmain.go
*.exe
*.test
*.prof
+
+.vscode/
\ No newline at end of file
diff --git a/.travis.yml b/.travis.yml
deleted file mode 100644
index 17364db..0000000
--- a/.travis.yml
+++ /dev/null
@@ -1,14 +0,0 @@
-language: go
-sudo: false
-
-go:
- - 1.x
- - master
-
-before_install:
- - go get -v golang.org/x/lint/golint
-
-script:
- - go vet ./...
- - golint ./...
- - go test -cover -v ./...
diff --git a/LICENSE.md b/LICENSE.md
old mode 100644
new mode 100755
diff --git a/README.md b/README.md
old mode 100644
new mode 100755
diff --git a/c14n.go b/c14n.go
new file mode 100755
index 0000000..457e6ec
--- /dev/null
+++ b/c14n.go
@@ -0,0 +1,85 @@
+package signedxml
+
+import (
+ // dsig "github.com/russellhaering/goxmldsig"
+ "github.com/lestrrat-go/libxml2/clib"
+ "github.com/lestrrat-go/libxml2/parser"
+)
+
+/*
+libxml2 based canonicalization:
+- C14N10 - v1.0 inclusive canonicalization, with or without comments
+- C14N11 - v1.1 inclusive canonicalization, with or without comments
+- C14N10Exclusive - v1.0 exclusive canonicalation, with or without comments: problem: can't pass
+namespace-prefix array with this
+
+*/
+
+type C14N10Canonicalizer struct {
+ WithComments bool
+}
+
+func (c C14N10Canonicalizer) Process(inputXML string,
+ transformXML string) (outputXML string, err error) {
+
+ // parse string with libxml2
+ p := parser.New()
+ doc, err := p.ParseString(inputXML)
+ if err != nil {
+ return "", err
+ }
+
+ // canonicalize
+ canonicalString, err := clib.XMLC14NDocDumpMemory(doc, 0, c.WithComments) // http://xmlsoft.org/html/libxml-c14n.html#xmlC14NMode
+ if err != nil {
+ return "", err
+ }
+
+ return canonicalString, nil
+}
+
+type C14N11Canonicalizer struct {
+ WithComments bool
+}
+
+func (c C14N11Canonicalizer) Process(inputXML string,
+ transformXML string) (outputXML string, err error) {
+
+ // parse string with libxml2
+ p := parser.New()
+ doc, err := p.ParseString(inputXML)
+ if err != nil {
+ return "", err
+ }
+
+ // canonicalize
+ canonicalString, err := clib.XMLC14NDocDumpMemory(doc, 2, c.WithComments) // http://xmlsoft.org/html/libxml-c14n.html#xmlC14NMode
+ if err != nil {
+ return "", err
+ }
+
+ return canonicalString, nil
+}
+
+type C14N10ExclusiveCanonicalizer struct {
+ WithComments bool
+}
+
+func (c C14N10ExclusiveCanonicalizer) Process(inputXML string,
+ transformXML string) (outputXML string, err error) {
+
+ // parse string with libxml2
+ p := parser.New()
+ doc, err := p.ParseString(inputXML)
+ if err != nil {
+ return "", err
+ }
+
+ // canonicalize
+ canonicalString, err := clib.XMLC14NDocDumpMemory(doc, 1, c.WithComments) // http://xmlsoft.org/html/libxml-c14n.html#xmlC14NMode
+ if err != nil {
+ return "", err
+ }
+
+ return canonicalString, nil
+}
diff --git a/envelopedsignature.go b/envelopedsignature.go
old mode 100644
new mode 100755
diff --git a/examples/canonicalization_test.go b/examples/canonicalization_test.go
new file mode 100644
index 0000000..072f5e1
--- /dev/null
+++ b/examples/canonicalization_test.go
@@ -0,0 +1,215 @@
+package examples
+
+import (
+ "fmt"
+ "testing"
+
+ "github.com/daugminas/signedxml"
+
+ . "github.com/smartystreets/goconvey/convey"
+)
+
+var example31Input = `
+
+
+
+
+
+Hello, world!
+
+
+
+
+
+`
+
+var example31Output = `
+Hello, world!
+`
+
+var example31OutputWithComments = `
+Hello, world!
+
+
+`
+
+var example32Input = `
+
+ A B
+
+ A
+
+ B
+ A B
+ C
+
+`
+
+var example32Output = `
+
+ A B
+
+ A
+
+ B
+ A B
+ C
+
+`
+
+var example33Input = `]>
+
+
+
+
+
+
+
+
+
+
+
+
+
+`
+
+var example33Output = `
+
+
+
+
+
+
+
+
+
+
+
+
+`
+
+var example34Input = `
+
+]>
+
+ First line
Second line
+ 2
+ "0" && value<"10" ?"valid":"error"]]>
+ valid
+
+
+`
+
+var example34Output = `
+ First line
+Second line
+ 2
+ value>"0" && value<"10" ?"valid":"error"
+ valid
+
+
+`
+
+// modified to not include DTD processing. still tests for whitespace treated as
+// CDATA
+var example34ModifiedOutput = `
+ First line
+Second line
+ 2
+ value>"0" && value<"10" ?"valid":"error"
+ valid
+
+
+`
+
+var example35Input = `
+
+
+
+
+]>
+
+ &ent1;, &ent2;!
+
+
+`
+
+var example35Output = `
+ Hello, world!
+`
+
+var example36Input = `
+©`
+
+var example36Output = "\u00A9"
+
+var example37Input = `
+
+]>
+
+
+
+
+
+
+`
+
+var example37SubsetExpression = `
+
+(//. | //@* | //namespace::*)
+[
+ self::ietf:e1 or (parent::ietf:e1 and not(self::text() or self::e2))
+ or
+ count(id("E3")|ancestor-or-self::node()) = count(ancestor-or-self::node())
+]`
+
+var example37Output = ``
+
+type exampleXML struct {
+ input string
+ output string
+ withComments bool
+ expression string
+}
+
+// test examples from the spec (www.w3.org/TR/2001/REC-xml-c14n-20010315#Examples)
+func TestCanonicalizationExamples(t *testing.T) {
+ Convey("Given XML Input", t, func() {
+ cases := map[string]exampleXML{
+ "(Example 3.1 w/o Comments)": {input: example31Input, output: example31Output},
+ "(Example 3.1 w/Comments)": {input: example31Input, output: example31OutputWithComments, withComments: true},
+ "(Example 3.2)": {input: example32Input, output: example32Output},
+ // 3.3 is for Canonical NOT ExclusiveCanonical (one of the exceptions here: http://www.w3.org/TR/xml-exc-c14n/#sec-Specification)
+ // "(Example 3.3)": {input: example33Input, output: example33Output},
+ "(Example 3.4)": {input: example34Input, output: example34ModifiedOutput},
+ // "(Example 3.5)": {input: example35Input, output: example35Output},
+ // 3.6 will work, but requires a change to the etree package first:
+ // http://stackoverflow.com/questions/6002619/unmarshal-an-iso-8859-1-xml-input-in-go
+ // "(Example 3.6)": {input: example36Input, output: example36Output},
+ "(Example 3.7)": {input: example37Input, output: example37Output, expression: example37SubsetExpression},
+ }
+ for description, test := range cases {
+ Convey(fmt.Sprintf("When transformed %s", description), func() {
+ transform := signedxml.ExclusiveCanonicalization{
+ WithComments: test.withComments,
+ }
+ resultXML, err := transform.Process(test.input, "")
+ Convey("Then the resulting XML match the example output", func() {
+ So(err, ShouldBeNil)
+ So(resultXML, ShouldEqual, test.output)
+ })
+ })
+ }
+ })
+}
+
+func TestTODO(t *testing.T) {
+ // The XML specifications cover the following examples, but our library does not successfully transform.
+ t.Logf("Input:\n%s\nOutput:\n%s\n", example33Input, example33Output) // Example 3.3
+ t.Logf("Input:\n%s\nOutput:\n%s\n", example35Input, example35Output) // Example 3.5
+ t.Logf("Input:\n%s\nOutput:\n%s\n", example36Input, example36Output) // Example 3.6
+ t.Logf("Input:\n%s\nOutput:\n%s\n", example37Input, example37Output) // Example 3.7
+}
diff --git a/examples/custom.go b/examples/custom.go
new file mode 100755
index 0000000..27cb539
--- /dev/null
+++ b/examples/custom.go
@@ -0,0 +1,89 @@
+package examples
+
+import (
+ "fmt"
+ "io/ioutil"
+ "os"
+
+ "github.com/beevik/etree"
+ "github.com/daugminas/signedxml"
+)
+
+func testLBmsgValidator() {
+
+ // read xml file
+ // xmlFile, err := os.Open("../testdata/LB/ilpnot.xml")
+ xmlFile, err := os.Open("../testdata/LB/iltsoinf.xml")
+ // xmlFile, err := os.Open("../testdata/LB/roinvstg.xml")
+ // xmlFile, err := os.Open("../testdata/LB/rsltnofinvstgtn.xml")
+ if err != nil {
+ panic(err)
+ }
+ defer xmlFile.Close()
+ xmlBytes, _ := ioutil.ReadAll(xmlFile)
+
+ // loax XML to validator
+ validator, err := signedxml.NewValidator(string(xmlBytes))
+ if err != nil {
+ panic(err)
+ }
+
+ // read cert
+ var certPEM string = "MIIFiDCCBHCgAwIBAgIKSZtmdgAAAAAA9TANBgkqhkiG9w0BAQUFADBdMQswCQYDVQQGEwJMVDEQMA4GA1UEBxMHVmlsbml1czEYMBYGA1UEChMPTGlldHV2b3MgYmFua2FzMQwwCgYDVQQLEwNNU0QxFDASBgNVBAMTC0xCLUxJVEFTLUNBMB4XDTExMDUyNjExMjMyOFoXDTQwMDgyNzA3NDMzM1owga8xMDAuBgoJkiaJk/IsZAEBDCBBNDI3QjFGM0M0MDFBMEQ0RTA0MzBBQzIwMzI5QTBENDEUMBIGA1UEBRMLMDAxMC8wMzUvMDExCzAJBgNVBAYTAkxUMRgwFgYDVQQKDA9MaWV0dXZvcyBiYW5rYXMxKTAnBgNVBAsMIE1va8SXamltbyBzaXN0ZW3FsyBkZXBhcnRhbWVudGFzMRMwEQYDVQQDDApURVNUIExJVEFTMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAl/bRLV1F9k1CCHPEedLbKVH3CDmrSlmNUfczG94tBabRdumiHTH14lopRkr6tS/2bN4xHU94Gd3Y9CdZ/lIBScJJVLlHJkZID8B7o4NyJXSSPOIg43fCbDO9q3eC/6WCTGklfmzhHxIOu5qqXetaWCtxDZKsLGJz625fRC/80Kw1JADMgQVl+mavj48LBQmkqfcHq5KDJQfHc1sthW+BGBdbNPE9wdGpmHQaCbt4fM3QczEhrHcDLSEvuFxUE9Z8WWLDs8GzrT4KPa5Y2MWZ5bdi2Rf3NiO46OLVuF97OcjsPQU5dx9jykd/ONTB8OOX1nEzK43d19zC8HkrlpFiYQIDAQABo4IB9TCCAfEwDgYDVR0PAQH/BAQDAgbAMIIBGwYDVR0gBIIBEjCCAQ4wggEKBgYEAI96AQIwgf8wgfwGCCsGAQUFBwICMIHvHoHsAFMAZQByAHQAaQBmAGkAawBhAHQAYQBzACAAbgBhAHUAZABvAGoAYQBtAGEAcwAgAHQAaQBrACAATABpAGUAdAB1AHYAbwBzACAAYgBhAG4AawBvACAAaQBuAGYAbwByAG0AYQBjAGkAbgEXAHMAZQAgAHMAaQBzAHQAZQBtAG8AcwBlAC4AIABGAG8AcgAgAHUAcwBhAGcAZQAgAGkAbgAgAHQAaABlACAASQBTACAAbwBmACAAdABoAGUAIABCAGEAbgBrACAAbwBmACAATABpAHQAaAB1AGEAbgBpAGEAIABvAG4AbAB5AC4wHQYDVR0OBBYEFCCU5ADFpsIgTc0MdhbwMr6oIAowMB8GA1UdIwQYMBaAFGCYgBf9iIkVmk22OnVwjPYOoFhzMDkGA1UdHwQyMDAwLqAsoCqGKGh0dHA6Ly93d3cubGIubHQvcGtpL2NybC9MQi1MSVRBUy1DQS5jcmwwRQYIKwYBBQUHAQEEOTA3MDUGCCsGAQUFBzAChilodHRwOi8vd3d3LmxiLmx0L3BraS9jZXJ0L0xCLUxJVEFTLUNBLmNydDANBgkqhkiG9w0BAQUFAAOCAQEArS4jZ0TeVLGJmwYMsNJNSzlMZ5GlnDzOHfNm/6/Dx1v0RfYJhL57V6H5REDCJzzdzemXsWLtYgjnP2UN1wTMcFcnYdRdbf7qrgSWdRCCQQlb8UOHct02bLyfOG4YzIsTqpYvcsmMu4dePquOabgLplGnSVWSHYsSgtkFXv7CR9e9bJ7QNvxj9hHQEtdyDOySEzkca784EqWS7x9R7m8Cyjj5EcZIgVN7s31kadZN3hoyMoqEeVc07z5SbKYDKxX7JHigzQeXlKYxQScJYov0JdzwKuH5LSy5+8kNruqz7y/KHRXiscrq6wEDmgfSx8NWW8KSj3ng75Nr+ZFx4AVSQg=="
+ cert, err := signedxml.LoadCertFromPEMString(certPEM, "CERTIFICATE")
+ if err != nil {
+ panic(err)
+ }
+ doc := etree.NewDocument()
+ err = doc.ReadFromBytes(xmlBytes)
+ if err != nil {
+ panic(err)
+ }
+ var certDigest, digestMethodURI string
+ if el := doc.FindElement(".//CertDigest/DigestMethod"); el != nil {
+ digestMethodURI = el.SelectAttrValue("Algorithm", "")
+ }
+ if el := doc.FindElement(".//CertDigest/DigestValue"); el != nil {
+ certDigest = el.Text()
+ }
+ err = signedxml.ValidateCertificate(cert, certDigest, digestMethodURI, "", "")
+ if err != nil {
+ panic(err)
+ }
+
+ // set LB cert to validator - avoid fails in ref
+ validator.SetValidationCert(cert)
+
+ // validate XML references (digests & signature)
+ err = validator.Validate()
+ if err != nil {
+ panic(err)
+ }
+
+ fmt.Println("Example Validation Succeeded")
+}
+
+func testOwnSignedDocValidator() {
+ // xmlFile, err := os.Open("../testdata/own/minimal_signed.xml")
+ // xmlFile, err := os.Open("../testdata/own/minimal_signed_RSAKeyValue.xml")
+ xmlFile, err := os.Open("../testdata/own/pacs008_signed.xml")
+ if err != nil {
+ fmt.Println("Error opening file:", err)
+ return
+ }
+ defer xmlFile.Close()
+
+ xmlBytes, _ := ioutil.ReadAll(xmlFile)
+
+ validator, err := signedxml.NewValidator(string(xmlBytes))
+ if err != nil {
+ fmt.Printf("Validation Error: %s", err)
+ } else {
+ err = validator.Validate()
+ if err != nil {
+ fmt.Printf("Validation Error: %s", err)
+ } else {
+ fmt.Println("Example Validation Succeeded")
+ }
+ }
+}
diff --git a/examples/examples.go b/examples/examples.go
old mode 100644
new mode 100755
index 98196f0..da176e8
--- a/examples/examples.go
+++ b/examples/examples.go
@@ -1,17 +1,19 @@
-package main
+package examples
import (
"fmt"
"io/ioutil"
"os"
- "github.com/ma314smith/signedxml"
+ "github.com/daugminas/signedxml"
)
-func main() {
- testValidator()
- testExclCanon()
-}
+// func main() {
+// // testValidator()
+// // testExclCanon()
+// // testLBmsgValidator()
+// testOwnSignedDocValidator()
+// }
func testValidator() {
xmlFile, err := os.Open("../testdata/valid-saml.xml")
diff --git a/exclusivecanonicalization.go b/exclusivecanonicalization.go
old mode 100644
new mode 100755
diff --git a/go.mod b/go.mod
new file mode 100755
index 0000000..bb890fe
--- /dev/null
+++ b/go.mod
@@ -0,0 +1,17 @@
+module github.com/daugminas/signedxml
+
+go 1.17
+
+require (
+ github.com/beevik/etree v1.1.0
+ github.com/lestrrat-go/libxml2 v0.0.0-20201123224832-e6d9de61b80d
+ github.com/smartystreets/goconvey v1.7.2
+)
+
+require (
+ github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1 // indirect
+ github.com/jtolds/gls v4.20.0+incompatible // indirect
+ github.com/pkg/errors v0.9.1 // indirect
+ github.com/smartystreets/assertions v1.2.0 // indirect
+ github.com/stretchr/testify v1.7.0 // indirect
+)
diff --git a/go.sum b/go.sum
new file mode 100755
index 0000000..efdb781
--- /dev/null
+++ b/go.sum
@@ -0,0 +1,29 @@
+github.com/beevik/etree v1.1.0 h1:T0xke/WvNtMoCqgzPhkX2r4rjY3GDZFi+FjpRZY2Jbs=
+github.com/beevik/etree v1.1.0/go.mod h1:r8Aw8JqVegEf0w2fDnATrX9VpkMcyFeM0FhwO62wh+A=
+github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8=
+github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
+github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1 h1:EGx4pi6eqNxGaHF6qqu48+N2wcFQ5qg5FXgOdqsJ5d8=
+github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY=
+github.com/jtolds/gls v4.20.0+incompatible h1:xdiiI2gbIgH/gLH7ADydsJ1uDOEzR8yvV7C0MuV77Wo=
+github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU=
+github.com/lestrrat-go/libxml2 v0.0.0-20201123224832-e6d9de61b80d h1:7uUkdtm6TC3olmG0I9lIAwBJQianl8YT5H8zcw6Mkpk=
+github.com/lestrrat-go/libxml2 v0.0.0-20201123224832-e6d9de61b80d/go.mod h1:fy/ZVbgyB83mtricxwSW3zqIRXWOVpKG2PvdUDFeC58=
+github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
+github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
+github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
+github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
+github.com/smartystreets/assertions v1.2.0 h1:42S6lae5dvLc7BrLu/0ugRtcFVjoJNMC/N3yZFZkDFs=
+github.com/smartystreets/assertions v1.2.0/go.mod h1:tcbTF8ujkAEcZ8TElKY+i30BzYlVhC/LOxJk7iOWnoo=
+github.com/smartystreets/goconvey v1.7.2 h1:9RBaZCeXEQ3UselpuwUQHltGVXvdwm6cv1hgR6gDIPg=
+github.com/smartystreets/goconvey v1.7.2/go.mod h1:Vw0tHAZW6lzCRk3xgdin6fKYcG+G3Pg9vgXWeJpQFMM=
+github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
+github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY=
+github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
+golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
+golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
+golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
+golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
+golang.org/x/tools v0.0.0-20190328211700-ab21143f2384/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
+gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
+gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo=
+gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
diff --git a/signedxml.go b/signedxml.go
old mode 100644
new mode 100755
index f14578c..7753696
--- a/signedxml.go
+++ b/signedxml.go
@@ -47,6 +47,16 @@ func init() {
"http://www.w3.org/2000/09/xmldsig#enveloped-signature": EnvelopedSignature{},
"http://www.w3.org/2001/10/xml-exc-c14n#": ExclusiveCanonicalization{},
"http://www.w3.org/2001/10/xml-exc-c14n#WithComments": ExclusiveCanonicalization{WithComments: true},
+
+ // xmllib2 canonicalizers, added:
+ // "http://www.w3.org/TR/xml-c14n": C14N10Canonicalizer{},
+ // "http://www.w3.org/TR/xml-c14n#WithComments": C14N10Canonicalizer{WithComments: true},
+ "http://www.w3.org/TR/2001/REC-xml-c14n-20010315": C14N10Canonicalizer{},
+ "http://www.w3.org/TR/2001/REC-xml-c14n-20010315#WithComments": C14N10Canonicalizer{WithComments: true},
+ // "http://www.w3.org/TR/xml-exc-c14n": C14N10ExclusiveCanonicalizer{},
+ // "http://www.w3.org/TR/xml-exc-c14n#WithComments": C14N10ExclusiveCanonicalizer{WithComments: true},
+ "http://www.w3.org/2006/12/xml-c14n11": C14N11Canonicalizer{},
+ "http://www.w3.org/2006/12/xml-c14n11#WithComments": C14N11Canonicalizer{WithComments: true},
}
}
@@ -117,20 +127,21 @@ func (s *signatureData) parseSignedInfo() error {
}
// move the Signature level namespace down to SignedInfo so that the signature
- // value will match up
- if s.signedInfo.Space != "" {
- attr := s.signature.SelectAttr(s.signedInfo.Space)
+ // value will match up I.e: check if SignedInfo prefix is defined in Signature, copy it to SignInfo attrs
+ if s.signedInfo.Space != "" { // if SignedInfo has a prefix
+ attr := s.signature.SelectAttr(s.signedInfo.Space) // find prefix definition in Signature
if attr != nil {
- s.signedInfo.Attr = []etree.Attr{*attr}
+ s.signedInfo.Attr = []etree.Attr{*attr} // copy the definition to SignedInfo
}
- } else {
- attr := s.signature.SelectAttr("xmlns")
+ } else { // if no prefix
+ attr := s.signature.SelectAttr("xmlns") // select any attribute with root namespace, if there is such
if attr != nil {
s.signedInfo.Attr = []etree.Attr{*attr}
}
}
// Copy SignedInfo xmlns: into itself if it does not exist and is defined as a root attribute
+ // i.e. check if SignedInfo prefix is defined in root, copy it to SignedInfo attrs
root := s.xml.Root()
if root != nil {
@@ -142,6 +153,15 @@ func (s *signatureData) parseSignedInfo() error {
}
}
+ // It is adding tag namespaces, even if it wasn't used in SignedInfo - mistake.
+ // Solution: add all namespaces, which are used in the SignedInfo child tags
+ // signedInfoDoc, err := populateElementWithNameSpaces(s.signedInfo, s.xml.Copy())
+ // if err != nil {
+ // return err
+ // }
+ // s.signedInfo.Parent().AddChild(signedInfoDoc.Root())
+ // s.signedInfo.Parent().RemoveChildAt(0) // old signedInfo
+
return nil
}
@@ -211,14 +231,30 @@ func (s *signatureData) getReferencedXML(reference *etree.Element, inputDoc *etr
// populate doc with the referenced xml from the Reference URI
if uri == "" {
outputDoc = inputDoc
+
+ // // the above does not remove XML declarations from the root doc,
+ // // this fixes it, though it should be done by canonicalization:
+ // outputDoc = etree.NewDocument()
+ // outputDoc.SetRoot(inputDoc.Root())
+
} else {
refIDAttribute := "ID"
if s.refIDAttribute != "" {
refIDAttribute = s.refIDAttribute
}
- path := fmt.Sprintf(".//[@%s='%s']", refIDAttribute, uri)
- e := inputDoc.FindElement(path)
- if e != nil {
+
+ // path := fmt.Sprintf(".//[@%s='%s']", refIDAttribute, uri)
+ // e := inputDoc.FindElement(path)
+ // if e != nil {
+ // outputDoc = etree.NewDocument()
+ // outputDoc.SetRoot(e.Copy())
+ if e := inputDoc.FindElement(fmt.Sprintf(".//[@%s='%s']", refIDAttribute, uri)); e != nil {
+ outputDoc = etree.NewDocument()
+ outputDoc.SetRoot(e.Copy())
+ } else if e := inputDoc.FindElement(fmt.Sprintf(".//[@%s='%s']", strings.ToLower(refIDAttribute), uri)); e != nil {
+ outputDoc = etree.NewDocument()
+ outputDoc.SetRoot(e.Copy())
+ } else if e := inputDoc.FindElement(fmt.Sprintf(".//[@%s='%s']", strings.Title(strings.ToLower(refIDAttribute)), uri)); e != nil {
outputDoc = etree.NewDocument()
outputDoc.SetRoot(e.Copy())
} else {
@@ -239,15 +275,21 @@ func (s *signatureData) getReferencedXML(reference *etree.Element, inputDoc *etr
return outputDoc, nil
}
-func getCertFromPEMString(pemString string) (*x509.Certificate, error) {
- pubkey := fmt.Sprintf("-----BEGIN PUBLIC KEY-----\n%s\n-----END PUBLIC KEY-----",
- pemString)
-
+func LoadCertFromPEMString(pemString, pubKeyType string) (*x509.Certificate, error) {
+ var pubkey string
+ switch {
+ case strings.EqualFold("PUBLIC KEY", pubKeyType):
+ pubkey = fmt.Sprintf("-----BEGIN PUBLIC KEY-----\n%s\n-----END PUBLIC KEY-----",
+ pemString)
+ case strings.EqualFold("CERTIFICATE", pubKeyType):
+ pubkey = fmt.Sprintf("-----BEGIN CERTIFICATE-----\n%s\n-----END CERTIFICATE-----",
+ pemString)
+ }
pemBlock, _ := pem.Decode([]byte(pubkey))
if pemBlock == nil {
return &x509.Certificate{}, errors.New("Could not parse Public Key PEM")
}
- if pemBlock.Type != "PUBLIC KEY" {
+ if pemBlock.Type != "PUBLIC KEY" && pemBlock.Type != "CERTIFICATE" {
return &x509.Certificate{}, errors.New("Found wrong key type")
}
@@ -296,7 +338,48 @@ func processTransform(transform *etree.Element,
return docOut, nil
}
-func calculateHash(reference *etree.Element, doc *etree.Document) (string, error) {
+// func calculateHash(reference *etree.Element, doc *etree.Document) (string, error) {
+// digestMethodElement := reference.SelectElement("DigestMethod")
+// if digestMethodElement == nil {
+// return "", errors.New("signedxml: unable to find DigestMethod")
+// }
+
+// digestMethodURI := digestMethodElement.SelectAttrValue("Algorithm", "")
+// if digestMethodURI == "" {
+// return "", errors.New("signedxml: unable to find Algorithm in DigestMethod")
+// }
+
+// digestAlgo, ok := hashAlgorithms[digestMethodURI]
+// if !ok {
+// return "", fmt.Errorf("signedxml: unable to find matching hash"+
+// "algorithm for %s in hashAlgorithms", digestMethodURI)
+// }
+
+// doc.WriteSettings.CanonicalEndTags = true
+// doc.WriteSettings.CanonicalText = true
+// doc.WriteSettings.CanonicalAttrVal = true
+
+// h := digestAlgo.New()
+// docBytes, err := doc.WriteToBytes()
+// if err != nil {
+// return "", err
+// }
+
+// // ioutil.WriteFile("C:/Temp/SignedXML/Suspect.xml", docBytes, 0644)
+// // s, _ := doc.WriteToString()
+// // logger.Println(s)
+
+// h.Write(docBytes)
+// d := h.Sum(nil)
+// calculatedValue := base64.StdEncoding.EncodeToString(d)
+
+// return calculatedValue, nil
+// }
+
+// calculates a hash of a TargetToBeHashed (*etree.Document or []byte), detecting
+// the hash algorithm in the reference element. If successful, hash digest value in
+// base64 encoded string is written to the reference element/DigestValue tag.
+func CalculateHashFromRef(reference *etree.Element, targetToBeHashed interface{}) (string, error) {
digestMethodElement := reference.SelectElement("DigestMethod")
if digestMethodElement == nil {
return "", errors.New("signedxml: unable to find DigestMethod")
@@ -313,23 +396,196 @@ func calculateHash(reference *etree.Element, doc *etree.Document) (string, error
"algorithm for %s in hashAlgorithms", digestMethodURI)
}
- doc.WriteSettings.CanonicalEndTags = true
- doc.WriteSettings.CanonicalText = true
- doc.WriteSettings.CanonicalAttrVal = true
+ var targetBytes []byte
+ var err error
+ switch v := targetToBeHashed.(type) {
+ case *etree.Document:
+ v.WriteSettings.CanonicalEndTags = true
+ v.WriteSettings.CanonicalText = true
+ v.WriteSettings.CanonicalAttrVal = true
+ targetBytes, err = v.WriteToBytes()
+ if err != nil {
+ return "", err
+ }
- h := digestAlgo.New()
- docBytes, err := doc.WriteToBytes()
- if err != nil {
- return "", err
+ case []byte:
+ targetBytes = v
}
- // ioutil.WriteFile("C:/Temp/SignedXML/Suspect.xml", docBytes, 0644)
- // s, _ := doc.WriteToString()
- // logger.Println(s)
+ // debug
+ // fn := strconv.FormatInt(time.Now().UnixNano(), 10) + ".xml" // unix-time based filename
+ // f, err := os.Create(fn)
+ // if err != nil {
+ // panic(err)
+ // }
+ // defer f.Close()
+ // _, err = f.Write(targetBytes)
+ // if err != nil {
+ // panic(err)
+ // }
+
+ h := digestAlgo.New() // hasher
+ h.Write(targetBytes) // calculate hash
+ d := h.Sum(nil) // digest
+ calculatedValue := base64.StdEncoding.EncodeToString(d) // digest in base64
- h.Write(docBytes)
- d := h.Sum(nil)
- calculatedValue := base64.StdEncoding.EncodeToString(d)
+ return calculatedValue, nil
+}
+
+// calculates a hash of a targetToBeHashed ([]byte), detecting the hash algorithm
+// by the URI string. The URI follows notation common for XML Signatures. If successfull,
+// it outputs base64 encoded string of a target hash digest (fingerprint).
+func CalculateHash(targetToBeHashed []byte, digestMethodURI string) (string, error) {
+
+ digestAlgo, ok := hashAlgorithms[digestMethodURI]
+ if !ok {
+ return "", fmt.Errorf("signedxml: unable to find matching hash"+
+ "algorithm for %s in hashAlgorithms", digestMethodURI)
+ }
+
+ h := digestAlgo.New() // hasher
+ _, err := h.Write(targetToBeHashed) // calculate hash
+ if err != nil {
+ return "", fmt.Errorf("signedxml: hashing error: %s", err)
+ }
+ d := h.Sum(nil) // digest
+ calculatedValue := base64.StdEncoding.EncodeToString(d) // digest in base64
return calculatedValue, nil
}
+
+// Copies all namespaces that related to the targetElement. It must have the following namespaces:
+// - own namespaces (if it defines such): nothing todo here, typically, they're defined in attributes of that element;
+// - if the element has a prefix, but no definition for it, then parent has this namespace defined;
+// - if any of the sub-elementas have a prefix, which is different from targetElement, then some parent must define it.
+// Needed before canonicalizing and calculating hash of the target Element.
+// TargetElem is always a sub-tag (child) of RootDoc
+func PopulateElementWithNameSpaces(targetElem *etree.Element, rootDoc *etree.Document) (err error) { //(outputDoc *etree.Document, err error) {
+
+ // check that targetElem is a child of rootDoc
+ if rootDoc.FindElement(".//"+targetElem.Tag) != nil {
+
+ // Step 1: cycle through all prefixes used in the targetElement,
+ // these will be namespace definitions we'll have to have in the element
+ nsDefinitions := getUsedPrefixes(targetElem)
+
+ // Step1.5: check if namespace definitions has an empty value (indicicator of
+ // default namespace). If it doesn't exists, check if any parents above the element
+ // have xmlns defined - if so, add this used nsDefinitions
+ if _, ok := nsDefinitions[""]; !ok { // if no empty k name exists
+ if checkIfParentsUseDefaultNS(targetElem, rootDoc) {
+ nsDefinitions[""] = ""
+ }
+ }
+
+ // Step 2: starting with the targetElem, work up the path until all
+ // prefix keys (namespace names) have their corresponding definitions collected
+ nsDefinitions = getNameSpaceDefinitions(nsDefinitions, targetElem, rootDoc)
+
+ // Step 3: populate the targetElem with the namespaces, relevant for it
+ // setNSDefinitionsDynamically(targetElem, nsDefinitions, []string{})
+ setNSDefinitions(targetElem, nsDefinitions)
+
+ } else if targetElem.FullTag() == rootDoc.FullTag() {
+ targetElem = rootDoc.Root()
+ } else {
+ err = errors.New("targetElem is not in the rootDoc, cannot copy namespaces")
+ }
+
+ return
+}
+
+// MOD: setts namespaces on the element, given in nsdef
+func setNSDefinitions(el *etree.Element, nsdef map[string]string) {
+ for k, v := range nsdef {
+ if k == "" {
+ el.CreateAttr("xmlns", v)
+ } else {
+ el.CreateAttr("xmlns:"+k, v)
+ }
+ }
+}
+
+// too complext, aimed at setting namespace where it is used
+func setNSDefinitionsDynamically(el *etree.Element, nsdef map[string]string, parentPrefixes []string) {
+
+ if el.Space != "" && !isInArray(el.Space, parentPrefixes) {
+ el.CreateAttr("xmlns:"+el.Space, nsdef[el.Space])
+ parentPrefixes = append(parentPrefixes, el.Space)
+ }
+
+ for _, c := range el.ChildElements() {
+ setNSDefinitionsDynamically(c, nsdef, parentPrefixes)
+ }
+}
+
+// checks if items is in array
+func isInArray(item string, array []string) bool {
+ for _, i := range array {
+ if item == i {
+ return true
+ }
+ }
+ return false
+}
+
+// returns a map, where its keys are the unique prefixes used in the
+// element and its children
+func getUsedPrefixes(el *etree.Element) (outMap map[string]string) {
+ // Space is element tag prefix. If it's emtpy, then this element has root namespace.
+ // if it's not empty, then it's defined somewhere up the element path.
+
+ outMap = map[string]string{}
+ outMap[el.Space] = "" // process element prefix
+ for _, c := range el.ChildElements() {
+ childMap := getUsedPrefixes(c) // process its children prefixes
+ for k, v := range childMap {
+ outMap[k] = v
+ }
+ }
+ return outMap
+}
+
+// checks if any of the parents above define default namespace (attribute "xmlns=...")
+func checkIfParentsUseDefaultNS(el *etree.Element, rootDoc *etree.Document) bool {
+ if attr := el.SelectAttr("xmlns"); attr != nil {
+ return true
+ }
+ upNext := rootDoc.FindElement(".//" + el.Tag).Parent()
+ if upNext != nil {
+ return checkIfParentsUseDefaultNS(upNext, rootDoc)
+ }
+ return false
+}
+
+// takes a map of prefixes and cycles up the path from the element to
+// collect its definitions
+func getNameSpaceDefinitions(prefixMap map[string]string, el *etree.Element, rootDoc *etree.Document) (outMap map[string]string) {
+ var weHaveUnfilledValues bool
+
+ for k, v := range prefixMap {
+ if v == "" {
+ weHaveUnfilledValues = true
+ if attr := el.SelectAttr(k); attr != nil {
+ prefixMap[k] = attr.Value
+ } else if attr := el.SelectAttr("xmlns:" + k); attr != nil {
+ prefixMap[k] = attr.Value
+ } else if attr := el.SelectAttr("xmlns"); attr != nil { // root NS
+ if _, ok := prefixMap[""]; ok { // make sure non-prefixed tags were in element
+ prefixMap[""] = attr.Value
+ }
+ }
+ }
+ }
+ upNext := rootDoc.FindElement(".//" + el.Tag).Parent()
+ if weHaveUnfilledValues && upNext != nil {
+ parentMap := getNameSpaceDefinitions(prefixMap, upNext, rootDoc)
+ for k, v := range parentMap {
+ if prefixMap[k] == "" && v != "" {
+ prefixMap[k] = v
+ }
+ }
+ }
+ outMap = prefixMap
+ return
+}
diff --git a/signedxml_test.go b/signedxml_test.go
old mode 100644
new mode 100755
diff --git a/signer.go b/signer.go
old mode 100644
new mode 100755
index d9abe47..3c77b90
--- a/signer.go
+++ b/signer.go
@@ -64,7 +64,7 @@ func (s *Signer) Sign(privateKey interface{}) (string, error) {
return "", err
}
}
- if err := s.parseSignedInfo(); err != nil {
+ if err := s.parseSignedInfo(); err != nil { // changes to SignedInfo tag
return "", err
}
if err := s.parseSigAlgorithm(); err != nil {
@@ -73,7 +73,7 @@ func (s *Signer) Sign(privateKey interface{}) (string, error) {
if err := s.parseCanonAlgorithm(); err != nil {
return "", err
}
- if err := s.setDigest(); err != nil {
+ if err := s.setDigest(); err != nil { // changes to Document, SignedProperties
return "", err
}
if err := s.setSignature(); err != nil {
@@ -96,20 +96,66 @@ func (s *Signer) setDigest() (err error) {
references := s.signedInfo.FindElements("./Reference")
for _, ref := range references {
doc := s.xml.Copy()
- transforms := ref.SelectElement("Transforms")
- for _, transform := range transforms.SelectElements("Transform") {
- doc, err = processTransform(transform, doc)
+ // transforms := ref.SelectElement("Transforms")
+ // if transforms != nil {
+ // for _, transform := range transforms.SelectElements("Transform") {
+ // doc, err = processTransform(transform, doc)
+ // if err != nil {
+ // return err
+ // }
+ // }
+ // }
+
+ // targetDoc, err := s.getReferencedXML(ref, doc)
+ // if err != nil {
+ // return err
+ // }
+
+ // MOD: 1. change order: 1st find the ref, then transform
+ // 2. if not root doc, add namespaces
+ targetDoc, err := s.getReferencedXML(ref, doc)
+ if err != nil {
+ return err
+ }
+
+ // copy relevant namespaces if the targetDoc is not the root document
+ if targetDoc.Root().Tag != s.xml.Root().Tag {
+
+ // if targetDoc element is not root (i.e, root sub-tag or child) being "digested",
+ // then populate with relevant namespaces
+ err = PopulateElementWithNameSpaces(targetDoc.Root(), s.xml.Copy())
if err != nil {
return err
}
}
- doc, err := s.getReferencedXML(ref, doc)
+ transforms := ref.SelectElement("Transforms")
+ if transforms != nil {
+ for _, transform := range transforms.SelectElements("Transform") {
+ targetDoc, err = processTransform(transform, targetDoc)
+ if err != nil {
+ return err
+ }
+ }
+ }
+
+ // MOD: canonicalization, defined at the signature level is mandatory for each
+ // reference before calculating the hash. This is to avoid situations when canonicalization
+ // is not explicitly defined in the of the Reference (it's implied).
+ // source: https://www.di-mgt.com.au/xmldsig2.html
+ targetDocStr, err := targetDoc.WriteToString()
+ if err != nil {
+ return err
+ }
+ targetDocStr, err = s.canonAlgorithm.Process(targetDocStr, "")
if err != nil {
return err
}
+ targetDoc2 := etree.NewDocument()
+ targetDoc2.ReadFromString(targetDocStr)
- calculatedValue, err := calculateHash(ref, doc)
+ // calculatedValue, err := calculateHash(ref, doc)
+ calculatedValue, err := CalculateHashFromRef(ref, targetDoc2)
if err != nil {
return err
}
@@ -136,7 +182,7 @@ func (s *Signer) setSignature() error {
return err
}
- var hashed, signature []byte
+ var digest, signature []byte
//var h1, h2 *big.Int
signingAlgorithm, ok := signingAlgorithms[s.sigAlgorithm]
if !ok {
@@ -145,16 +191,17 @@ func (s *Signer) setSignature() error {
hasher := signingAlgorithm.hash.New()
hasher.Write([]byte(canonSignedInfo))
- hashed = hasher.Sum(nil)
+ digest = hasher.Sum(nil)
switch signingAlgorithm.algorithm {
case "rsa":
- signature, err = rsa.SignPKCS1v15(rand.Reader, s.privateKey.(*rsa.PrivateKey), signingAlgorithm.hash, hashed)
+ // "RSASSA-PKCS1-v1_5" as in Section 8.2 of RFC8017 (https://tools.ietf.org/html/rfc8017)
+ signature, err = rsa.SignPKCS1v15(rand.Reader, s.privateKey.(*rsa.PrivateKey), signingAlgorithm.hash, digest)
/*
case "dsa":
- h1, h2, err = dsa.Sign(rand.Reader, s.privateKey.(*dsa.PrivateKey), hashed)
+ h1, h2, err = dsa.Sign(rand.Reader, s.privateKey.(*dsa.PrivateKey), digest)
case "ecdsa":
- h1, h2, err = ecdsa.Sign(rand.Reader, s.privateKey.(*ecdsa.PrivateKey), hashed)
+ h1, h2, err = ecdsa.Sign(rand.Reader, s.privateKey.(*ecdsa.PrivateKey), digest)
*/
}
if err != nil {
diff --git a/testdata/LB/cert.pem b/testdata/LB/cert.pem
new file mode 100755
index 0000000..a30f519
--- /dev/null
+++ b/testdata/LB/cert.pem
@@ -0,0 +1,3 @@
+-----BEGIN CERTIFICATE-----
+MIIFiDCCBHCgAwIBAgIKSZtmdgAAAAAA9TANBgkqhkiG9w0BAQUFADBdMQswCQYDVQQGEwJMVDEQMA4GA1UEBxMHVmlsbml1czEYMBYGA1UEChMPTGlldHV2b3MgYmFua2FzMQwwCgYDVQQLEwNNU0QxFDASBgNVBAMTC0xCLUxJVEFTLUNBMB4XDTExMDUyNjExMjMyOFoXDTQwMDgyNzA3NDMzM1owga8xMDAuBgoJkiaJk/IsZAEBDCBBNDI3QjFGM0M0MDFBMEQ0RTA0MzBBQzIwMzI5QTBENDEUMBIGA1UEBRMLMDAxMC8wMzUvMDExCzAJBgNVBAYTAkxUMRgwFgYDVQQKDA9MaWV0dXZvcyBiYW5rYXMxKTAnBgNVBAsMIE1va8SXamltbyBzaXN0ZW3FsyBkZXBhcnRhbWVudGFzMRMwEQYDVQQDDApURVNUIExJVEFTMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAl/bRLV1F9k1CCHPEedLbKVH3CDmrSlmNUfczG94tBabRdumiHTH14lopRkr6tS/2bN4xHU94Gd3Y9CdZ/lIBScJJVLlHJkZID8B7o4NyJXSSPOIg43fCbDO9q3eC/6WCTGklfmzhHxIOu5qqXetaWCtxDZKsLGJz625fRC/80Kw1JADMgQVl+mavj48LBQmkqfcHq5KDJQfHc1sthW+BGBdbNPE9wdGpmHQaCbt4fM3QczEhrHcDLSEvuFxUE9Z8WWLDs8GzrT4KPa5Y2MWZ5bdi2Rf3NiO46OLVuF97OcjsPQU5dx9jykd/ONTB8OOX1nEzK43d19zC8HkrlpFiYQIDAQABo4IB9TCCAfEwDgYDVR0PAQH/BAQDAgbAMIIBGwYDVR0gBIIBEjCCAQ4wggEKBgYEAI96AQIwgf8wgfwGCCsGAQUFBwICMIHvHoHsAFMAZQByAHQAaQBmAGkAawBhAHQAYQBzACAAbgBhAHUAZABvAGoAYQBtAGEAcwAgAHQAaQBrACAATABpAGUAdAB1AHYAbwBzACAAYgBhAG4AawBvACAAaQBuAGYAbwByAG0AYQBjAGkAbgEXAHMAZQAgAHMAaQBzAHQAZQBtAG8AcwBlAC4AIABGAG8AcgAgAHUAcwBhAGcAZQAgAGkAbgAgAHQAaABlACAASQBTACAAbwBmACAAdABoAGUAIABCAGEAbgBrACAAbwBmACAATABpAHQAaAB1AGEAbgBpAGEAIABvAG4AbAB5AC4wHQYDVR0OBBYEFCCU5ADFpsIgTc0MdhbwMr6oIAowMB8GA1UdIwQYMBaAFGCYgBf9iIkVmk22OnVwjPYOoFhzMDkGA1UdHwQyMDAwLqAsoCqGKGh0dHA6Ly93d3cubGIubHQvcGtpL2NybC9MQi1MSVRBUy1DQS5jcmwwRQYIKwYBBQUHAQEEOTA3MDUGCCsGAQUFBzAChilodHRwOi8vd3d3LmxiLmx0L3BraS9jZXJ0L0xCLUxJVEFTLUNBLmNydDANBgkqhkiG9w0BAQUFAAOCAQEArS4jZ0TeVLGJmwYMsNJNSzlMZ5GlnDzOHfNm/6/Dx1v0RfYJhL57V6H5REDCJzzdzemXsWLtYgjnP2UN1wTMcFcnYdRdbf7qrgSWdRCCQQlb8UOHct02bLyfOG4YzIsTqpYvcsmMu4dePquOabgLplGnSVWSHYsSgtkFXv7CR9e9bJ7QNvxj9hHQEtdyDOySEzkca784EqWS7x9R7m8Cyjj5EcZIgVN7s31kadZN3hoyMoqEeVc07z5SbKYDKxX7JHigzQeXlKYxQScJYov0JdzwKuH5LSy5+8kNruqz7y/KHRXiscrq6wEDmgfSx8NWW8KSj3ng75Nr+ZFx4AVSQg==
+-----END CERTIFICATE-----
diff --git a/testdata/LB/ilpnot.xml b/testdata/LB/ilpnot.xml
new file mode 100755
index 0000000..4bd056b
--- /dev/null
+++ b/testdata/LB/ilpnot.xml
@@ -0,0 +1,124 @@
+
+
+
+
+ LIABLT2XMSD
+ UAAMLT21XXX
+ 55
+ S192910006283297
+ 2019-10-18T08:04:44
+ LITAS-INST
+
+
+
+
+ S192910006283297
+
+ FormDoc
+ 2019-10-18T08:04:44
+
+ ILPNOT
+ 55
+ SEPAINST
+
+
+
+
+
+ O192910019289527
+ 2019-10-18T08:04:44
+
+
+ 96487
+ 2019-10-18T08:04:44
+
+
+
+ LT721020103010035400
+
+
+
+
+ LIABLT2XMSD
+
+
+
+
+
+
+ INFO
+
+
+ 55
+ CRDT
+
+ 2019-10-18
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ UR71lqxT1AqiiroWOcxhT7IxSnx2kkHbgi0TEXJSl7k=
+
+
+
+ R/wFOwKSleB9viuWSe/TZ1PrHY9+0K755hfVNCOQDYg=
+
+
+ kBI4DTDeOcGmhCn38nwGtpI+dHt9Qji2YEdDy4y9eRlKkYInEtoM1jwzp/PhMN5mm8kHM+CSHB1jFFt6JFSofGjWCpIW+wlDE8woi1JdMytsXpprIaxTp0IxkMM7gBB7CuJWpagqnP9NkD6Hd4qR6JQqSncpss/WqLfd5HBOlc2COym0V9yHoke9OUUdTi97Dx+wMqLaRw6bGl440So9A/YsQ9W7yUwdPDmdzBqxt2o2Ea4IAEflfsPXTjxh5exnN+0rcbGWTzJ9YuXwlUgbXRdT0Od+N+ibW+c2H1bZ4VlVBX39DA6NqaTTrX3Erg4f8tt1TkP9dbp5ApRiNhFpRg==
+
+
+
+
+
+
+ 2019-10-18T08:06:12+03:00
+
+
+
+
+ 8qgOegrfKqAjvODPkq7ctJm4YHc=
+
+
+ C=LT, L=Vilnius, O=Lietuvos bankas, OU=MSD, CN=LB-LITAS-CA
+ 347599381669548201607413
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ ancestor-or-self::dsig:SignatureValue
+
+
+
+ MIICZwYJKoZIhvcNAQcCoIICWDCCAlQCAQMxCzAJBgUrDgMCGgUAMGkGCyqGSIb3DQEJEAEEoFoEWDBWAgEBBgYEAM4PAQEwITAJBgUrDgMCGgUABBQOAcijvfNWRXoxMrg7fL0yHYOYPAIQFmyl2zcGDKJMyaC2/fKJuBgPMjAxOTEwMTgwNTA2MTJaMAMCAQExggHVMIIB0QIBATBrMF0xCzAJBgNVBAYTAkxUMRAwDgYDVQQHEwdWaWxuaXVzMRgwFgYDVQQKEw9MaWV0dXZvcyBiYW5rYXMxDDAKBgNVBAsTA01TRDEUMBIGA1UEAxMLTEItTElUQVMtQ0ECCkmb8KUAAAAAAPYwCQYFKw4DAhoFAKBBMBoGCSqGSIb3DQEJAzENBgsqhkiG9w0BCRABBDAjBgkqhkiG9w0BCQQxFgQUaJaKvHeN49uPY6dd1w9dkjKy1hEwDQYJKoZIhvcNAQEBBQAEggEAQYh4Of+9HNnI5wUEVhCBy0Cgyn9q6yEjg7qIKu3LALpUQrw1kWZf8EJH6S40qfH8zl53vpBYc/uMPso50BPFHGMHpj0j4wTsNNtqlAzFypFE2qayjYyk7kWr+MHVy6V9WqD4suyRucb00S1qsRtrxcs2XT26eOf06LlLeMHXTkPdkndniy0g2fT+Beqej3mwG40qfFTA8JiUacRRNUz8hBEhbU6acyXPIF4zhL9nGLPnwq1v8/h+3/Ks64pImbtPLXVOAVpFplBnzJchajT460dMRZLy15cMruPTw3FDEoPbgunj66VC4r+13JEObSFnMyoO67EZ2matMHx7kTt/Sw==
+
+
+
+
+
+
+
diff --git a/testdata/LB/iltsoinf.xml b/testdata/LB/iltsoinf.xml
new file mode 100755
index 0000000..afeb9c5
--- /dev/null
+++ b/testdata/LB/iltsoinf.xml
@@ -0,0 +1,2 @@
+
+LIABLT2XMSDTRYULT21XXX55S1928400062777132019-10-11T07:59:54LITAS-PHAS192840006277713FormDoc2019-10-11T07:59:54ILTSOINF55SEPAINSTS1928400062777132019-10-11T07:59:5412.5O192840019280677TRYULT21XXXLT831020103000037800O192840019280674O1928400192806742.52019-10-11LIABLT2XINSLT0610201030002101008rqSzUtkmYsoi+gidJOKS/k2B4M=UPxuddCeQoBr97hRj8bY5RZoerE=ApJR4siWoLVqvmIiIs9yFn+71gnWX6d5000Xkqa6WbJ8AymQOrF/SEfapPeVEGdBSYLOZWYASHNwUq77qFOSoDyCbsRN9pCnqJQWUP0BT9eOEXeDNDLr8P8o56gR3OAEoGrMABY5vPBGzqnOCh0Rzr6nBDwmZJInIhnI2IuL7Hm5K77d/EUMWz0ry9b8eGls0h/5osGPRKbYucIolV2184f3TkxF52lgoUjT1e+T6sHUFsAkU2p2roezSZLoeIXuuTadNH5WfeAZhSplcQ0xb+Y12Mqm+071WXBbhci+L9VNsUoY8748vbO1javmjKpEEXLm/r+xLa2uuJbGr4xrhg==2019-10-11T08:00:23+03:008qgOegrfKqAjvODPkq7ctJm4YHc=C=LT, L=Vilnius, O=Lietuvos bankas, OU=MSD, CN=LB-LITAS-CA347599381669548201607413ancestor-or-self::dsig:SignatureValueMIICaAYJKoZIhvcNAQcCoIICWTCCAlUCAQMxCzAJBgUrDgMCGgUAMGoGCyqGSIb3DQEJEAEEoFsEWTBXAgEBBgYEAM4PAQEwITAJBgUrDgMCGgUABBSSsMfbqaAsZnGbBx3kcRFXDetW+gIRANYSQBeS/s6NScF2GsjJ+8YYDzIwMTkxMDExMDUwMDIzWjADAgEBMYIB1TCCAdECAQEwazBdMQswCQYDVQQGEwJMVDEQMA4GA1UEBxMHVmlsbml1czEYMBYGA1UEChMPTGlldHV2b3MgYmFua2FzMQwwCgYDVQQLEwNNU0QxFDASBgNVBAMTC0xCLUxJVEFTLUNBAgpJm/ClAAAAAAD2MAkGBSsOAwIaBQCgQTAaBgkqhkiG9w0BCQMxDQYLKoZIhvcNAQkQAQQwIwYJKoZIhvcNAQkEMRYEFBCeO79048MXXY2QdJDgsXhysRD4MA0GCSqGSIb3DQEBAQUABIIBAGaKtqXndSfa4XqaSIu/OZv6zfLUpsI4fVbiqBOKrtUvEXQgt8XUdQBaHJifasnalsOmQ38LiQfO4W5nXa1w1OmjRDPN/F5Q9faFF0rcKNwYICWxX1OxKURkDOLuxCdmQ5rwE5Zv5c3XgfdkaEad2NLJijH3xlW1CJreCP089CGOVVgT6pWbxc+AgOBWIYLBdc8H33Yzetd7+wIjoizLHdwLuVJ+evHlHTXJmDTZaTdCAF39evDa5GCL8NjbMEoCWuRCQHmYIVO7bluzF+KKh44qPUEx2OjQjO9xPPqRh0Rg2Mw6VuV+6CLSEo54HsJDFNP7rfb6p9IPFMobmdZralE=
diff --git a/testdata/LB/intermediate.pem b/testdata/LB/intermediate.pem
new file mode 100755
index 0000000..542a0a4
--- /dev/null
+++ b/testdata/LB/intermediate.pem
@@ -0,0 +1,3 @@
+-----BEGIN CERTIFICATE-----
+MIIFYDCCA0igAwIBAgIKYWSxSAAAAAAAAzANBgkqhkiG9w0BAQUFADBiMQswCQYDVQQGEwJMVDEQMA4GA1UEBxMHVmlsbml1czEYMBYGA1UEChMPTGlldHV2b3MgYmFua2FzMQwwCgYDVQQLEwNNU0QxGTAXBgNVBAMTEExCLUxJVEFTLVJPT1QtQ0EwHhcNMTAwODI3MDczMzMzWhcNNDAwODI3MDc0MzMzWjBdMQswCQYDVQQGEwJMVDEQMA4GA1UEBxMHVmlsbml1czEYMBYGA1UEChMPTGlldHV2b3MgYmFua2FzMQwwCgYDVQQLEwNNU0QxFDASBgNVBAMTC0xCLUxJVEFTLUNBMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAs5+VO3eKDFB1dVGkmFvAtGLyXLrTCdPfTd6hvm5qGQ5hjC0gx/SgBp2QSPnRXWWccoGCWYYuWgc900B0ln+boBjzfLqFKMxIhabrwS7vJIQX2imM1SNSq65PYA1xlC7GVYxpmn407f2TnXJJtjvMSCkCZVVoVqR97Qe5LQ+Qtq8+AugyVBPGeM15yBy65iuIhgpw55u4RNU1TtQ/6ECaH+0Hv0HAmoSRmT1MUYd4IRgC+jMRLUEnXYYMjEomVngVL5Peb3MN6tPxSlkK75TXADJkUwwxTP6pwU0+83UmWSb9EPxKvD+VpdP2s7YT/Saav3w0x7qCJoQC57/VJ4CtEQIDAQABo4IBGzCCARcwEAYJKwYBBAGCNxUBBAMCAQAwHQYDVR0OBBYEFGCYgBf9iIkVmk22OnVwjPYOoFhzMBkGCSsGAQQBgjcUAgQMHgoAUwB1AGIAQwBBMAsGA1UdDwQEAwIBhjAPBgNVHRMBAf8EBTADAQH/MB8GA1UdIwQYMBaAFFl0eSGRBuXzGc6uCf4GzSCF9+VMMD4GA1UdHwQ3MDUwM6AxoC+GLWh0dHA6Ly93d3cubGIubHQvcGtpL2NybC9MQi1MSVRBUy1ST09ULUNBLmNybDBKBggrBgEFBQcBAQQ+MDwwOgYIKwYBBQUHMAKGLmh0dHA6Ly93d3cubGIubHQvcGtpL2NlcnQvTEItTElUQVMtUk9PVC1DQS5jcnQwDQYJKoZIhvcNAQEFBQADggIBAINaMR7OGai71ULxTgtEkcxF4O0reivcFNUh2UddxadWDN1st8YTC1HSOu43NGWo+V77dxe7aP8e6p9OiEIgdzJWOvDxoA6JVFz1AN0JPMpXzwj+M19Y+kM5ncqdCjrB4NcXYxJvcjyOlc1UNfIdAGG8o15H4HEWzreIE3o0JxlZD/JIjBzkhNYVJxvJyK7ErO0YuDycOvYNGiOG521nz7UoPYJY/lb03rPjZ3wC0ayrcEPFRobbYAmSqLOAK9S6DHPVgWNNVIowbbmfOQT/vNIEZ1mmWvCx1U83mOrvZiMpmbHy02AS21k0yvWZq/yHQd/+CXKtSldldE5Z+IaQl4n0Ll6aV2UjbVu79yd9JW1py0bh4lciEXp7M3lQQ/4m/EiMoMEf31Tjqh9k+8i6AinN1fVmb5BnYeCtB6r9Pz9ppNC8/ezFdlrebdJNUZFEDAt5XucTHUQ8jYjrdeimqjQyRfKqiTmJ0BRN7zJyq2U1dWC3rL8vQbvwIUjModFEk9Bo6PZSJ8CrN4h85awSewRwqPTZMEQxYOWsNJzxfIFrCIH7O8LrRRcRXITb5yyYibWvpJ+5sqpeeVdtwR4uPk9LSJE62PCs3g7JDYnYdXYgSfxooFaJlYS0TlatXW3xW4G4eJEkowM1YMzr10E2r9/fNi08QqdEgW9+N9H8jqpv
+-----END CERTIFICATE-----
diff --git a/testdata/LB/roinvstg.xml b/testdata/LB/roinvstg.xml
new file mode 100755
index 0000000..7810d76
--- /dev/null
+++ b/testdata/LB/roinvstg.xml
@@ -0,0 +1,178 @@
+
+
+
+
+ LIABLT2XMSD
+ BANDLT21XXX
+ 51
+ S182550005953387
+ 2018-09-12T11:37:17
+ LITAS-RLS
+
+
+
+
+ S182550005953387
+
+ FormDoc
+ 2018-09-12T11:37:17
+
+ ROINVSTG
+ 51
+ SEPASCT
+
+
+
+
+
+ S182550005953387
+
+
+
+ LIABLT2XMSD
+
+
+
+
+
+
+ BANDLT21XXX
+
+
+
+ 2018-09-12T11:37:17
+
+
+ RJCR
+
+
+
+ RT01536741376723
+
+ FFCCTRNS
+ pacs.008.001.02
+
+ NOTPROVIDED
+ va001
+ RJCR
+
+
+
+
+ VAUALT21XXX
+
+
+
+
+ AC04
+
+
+
+ 11.00
+ 2018-09-12
+
+ CLRG
+
+ LITAS-RLS
+
+
+
+
+ SEPA
+
+
+
+ Bandomasis
+
+
+
+ LT950100100000123456
+
+
+
+
+ BANDLT21XXX
+
+
+
+
+ VAUALT21XXX
+
+
+
+ Frodo Baggins
+
+
+
+ LT183070020100000030
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ E6QSUeOPHp+E6USDMrN37nL9iVQ=
+
+
+
+ WvY17Gcsp7aHX9M6pwl5Fgbam1k=
+
+
+ Pr62NMjfDtZnzRFgJq9jpZdm0230y0sYM2nv9zzs2hczTcJ3deUCfjYhlScEG3Xep9+joASRTyJLcELlxSgMFyXVxI5pTzwDbFcnH6HH1RW1K7szazL4SmLS3grcNxKY/2erhnxS/84vILVE2zRGE8rOLTenZwmy+bcKw9RTblaGQ6xTh3MKN2JkhGAOPPQAmR8G8ADb1vgpklRDaDL0GG65xq3wUOWeHkiI4LDTfEFj+YyNf1IBpdPKPecvhk3yTkX9xoHzdwNKgdXvEkDlAQv0Azxu3110+y2HAJ1EjL51UrRDuJwrSdZeSUZ6OSP7kwqA8iovlL3hIaV7ehDLQQ==
+
+
+
+
+
+
+ 2018-09-12T11:37:43+03:00
+
+
+
+
+ 8qgOegrfKqAjvODPkq7ctJm4YHc=
+
+
+ C=LT, L=Vilnius, O=Lietuvos bankas, OU=MSD, CN=LB-LITAS-CA
+ 347599381669548201607413
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ ancestor-or-self::dsig:SignatureValue
+
+
+
+ MIICaAYJKoZIhvcNAQcCoIICWTCCAlUCAQMxCzAJBgUrDgMCGgUAMGoGCyqGSIb3DQEJEAEEoFsEWTBXAgEBBgYEAM4PAQEwITAJBgUrDgMCGgUABBStpG85eOnt0jfxeHARhVrpWMbMXgIRAJi42HyeGOiFQlFK6yW/FFgYDzIwMTgwOTEyMDgzNzQzWjADAgEBMYIB1TCCAdECAQEwazBdMQswCQYDVQQGEwJMVDEQMA4GA1UEBxMHVmlsbml1czEYMBYGA1UEChMPTGlldHV2b3MgYmFua2FzMQwwCgYDVQQLEwNNU0QxFDASBgNVBAMTC0xCLUxJVEFTLUNBAgpJm/ClAAAAAAD2MAkGBSsOAwIaBQCgQTAaBgkqhkiG9w0BCQMxDQYLKoZIhvcNAQkQAQQwIwYJKoZIhvcNAQkEMRYEFKf7Xxi5gkNFNNl/3vc0Zl6CL5TAMA0GCSqGSIb3DQEBAQUABIIBAMH2FrDenbnZ0qKJ8wqnkd2BAHX3bDm6//ILu5eERwrU5tIQSJqAZmufvzbIy06xvkNTCFCNdy/UM9wIv7oCFOJn+RGfSRD1SqdJ6o+VVXb5KdFHMOoIg9wyBDRGeJa/2MaAKC5SmbyDhlmtfy44NpOaJdW77kBI5VBmgvigh4IJsYy1T9Rjf4fTpW605Y5dA8hmMiY43tVMO1kSLdpyMe68v8OLNxp6wWhoxisLvKrgEM3dZmpXxEskzlD5xxhhsHkkYHEFUMRPPCow0fx/xpaSTNvnY+n5IYpLMG3lvzQmsd8Ct59m93z2Gvxz8eYwaAVKBV1Cnjc+AUz4Ku1APDo=
+
+
+
+
+
+
+
diff --git a/testdata/LB/root-ca.pem b/testdata/LB/root-ca.pem
new file mode 100755
index 0000000..9478c03
--- /dev/null
+++ b/testdata/LB/root-ca.pem
@@ -0,0 +1,3 @@
+-----BEGIN CERTIFICATE-----
+MIIFoTCCA4mgAwIBAgIQOPX+M4cXw69LDS0bYk/z9DANBgkqhkiG9w0BAQUFADBiMQswCQYDVQQGEwJMVDEQMA4GA1UEBxMHVmlsbml1czEYMBYGA1UEChMPTGlldHV2b3MgYmFua2FzMQwwCgYDVQQLEwNNU0QxGTAXBgNVBAMTEExCLUxJVEFTLVJPT1QtQ0EwIBcNMTAwODI3MDU1MDEwWhgPMjA3MDA4MjcwNTU5NTBaMGIxCzAJBgNVBAYTAkxUMRAwDgYDVQQHEwdWaWxuaXVzMRgwFgYDVQQKEw9MaWV0dXZvcyBiYW5rYXMxDDAKBgNVBAsTA01TRDEZMBcGA1UEAxMQTEItTElUQVMtUk9PVC1DQTCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBAMqlQVbTKw8lDj2EKwlFv5iIOAGaRV02/W3Z2CApbv4E7W7nfry0nEgeLe/JzBDQJpXB0ABrP7YMGC6J2q1PbViVdG3uLFtbxWTCaHGw5eJELKtxQSj+xzaQQDzGlNQvDzcgm/tQPnh0+J86ZMSiW1c88yhXX7rMEoQIaSEsaQInnB4Jwe5/ADGPrvE939+hrIYSRLIlLjBRAdvwTkcXMPZE79vBYWumVsxmYjcLpzm0q69146VFeREyPHVg0FhVK6YOF5H7ckTq8/e7Lw1jgcV9ozDJTgW9sqVwY5EQhK9V+YFr0oLAmDCIDI+OGDl+JDwktMv4kkk8YmWgcWH541XIyoM8J2j2CAH5I2pGEY87saaV1JROSzgg8wF5OZMauFiKZ099FGqhIsfrBJLnurI1AekS2SdL3AH8NFzJ+bV+A+uzOmDCbmY0O67TPPuZv1SNavUABbB3p0jc22z7obqRAvfOq00ctlEYTBygmE1Xr3D68UvEAFq30ZyS3twBi/HIoGl4gG2ygQv7EmDhCkNu+tLLWGb4bIVWIjJpUJdJrEhq3xBqFY4fiWb8tIxy4/iPGdFkQQ5rvjCHORLwi71ZR7nCFLLebdG+a3SbRSGpG9q/vRIYw31IynzEGzo4vEfmDVHyVib5YQqcLA7pGGnvYHQncan7laOM20x892nxAgMBAAGjUTBPMAsGA1UdDwQEAwIBhjAPBgNVHRMBAf8EBTADAQH/MB0GA1UdDgQWBBRZdHkhkQbl8xnOrgn+Bs0ghfflTDAQBgkrBgEEAYI3FQEEAwIBADANBgkqhkiG9w0BAQUFAAOCAgEAMjDbJYVemx/EJqlHEOCozq2pQUjEaGNRx0Z3IcdR3i6ARayg3ITvM38TKP6rqC8T3mdstPs5/Vuf6NacKiOgLJjSEEyc6FNPah+EN7aNjHJucfr2PsyfxKtxMdrEHakf3J02D8DK+5/+3iQTp/iT4jAxSsrGI3cn1bwCtbBEEElXXBRerQglZITmcrf7Qf5nAxZ9OFVZT1AmUGTZNz45dts3QXd8OGs9UopV3LFCuXerbElOZFx2lK2X0PT+pZknfDvDIhi2vCpMSegbGLClt0/Wgsc7dPRNUBYx+aPsaZv1/pYCsI2muHiYTemBt9gm/d4yVrb0IqExc0xWa3CYS68QWyLJFDvuVIu6AtI/Oo7WS4pGKbfI+D3pJquaSwPZYFnzHg3Cix/LL9dpOe1HqBotiDmP4Z+eDPGlWLj/0RB19Y5oxVUR10AwTSE6qk9aWgtDeT//cqyPEIaqrpDpfQ01/FiisEfT/ZCpiMEFI9ruIEnGIprJM520AdJwCzz4UzQUWpO6KuOAVBz0gz2vBeEo5/e/Yex/XLciPmHArHkMahNR2zDKYU3aRGbPPHZgUB1pZRPN4AfOUxQU9mcGoM7RSa9Ky164GOOhYTnxSpXIWdIgAufj3OUV1edlHuYl8w/Bk51isZVweew4FpQ40/k0yVfk+yo5Kcup7Vi4Tio=
+-----END CERTIFICATE-----
diff --git a/testdata/LB/rsltnofinvstgtn.xml b/testdata/LB/rsltnofinvstgtn.xml
new file mode 100755
index 0000000..0baebf3
--- /dev/null
+++ b/testdata/LB/rsltnofinvstgtn.xml
@@ -0,0 +1,107 @@
+
+
+
+ ABLPLT21XXX
+ LIABLT2XMSD
+ 51
+ 01191003000004B9
+ 2019-10-03T09:39:20
+ LITAS-MIG
+
+
+
+
+ 01191003000004B9
+
+ FormDoc
+ 2019-10-03T09:39:20
+
+ RESINV
+ 51
+ SEPAQuery
+
+
+
+
+
+ 01191003000004B9
+
+
+
+ ABLPLT21XXX
+
+
+
+
+
+
+ LIABLT2XMSD
+
+
+
+ 2019-10-03T09:39:20
+
+
+ 01191003000004B9
+
+
+
+ ABLPLT20XXX
+
+
+
+
+
+ MODI
+
+
+ 4S1927400062680804
+
+ S192740006268080
+ pacs.008.001.02
+
+ NOTPROVIDED
+ txid.2019-09-30.14:52:11.45d4dd8
+
+
+ CLRG
+
+ ST2
+
+
+
+
+ SEPA
+
+
+
+
+ FR7612345678900001234567858
+
+
+
+
+ SATPFRP0
+
+
+
+
+ ABLPLT20XXX
+
+
+
+
+ LT023030000000123456
+
+
+
+
+
+ 2019-10-02
+
+
+
+
+
+
+AeljTX3XdhMEVFaHunp2OA0o958WIxPBz7RJlUoXVhU=Pj1h8jNnLa144y5RM7dvi4qhlwJbskyj+h/vE4XOzv0=rY9qedkv6z31RdLGyCuSjJpODrJzwtb/8vOxxufHlw7rnPaPimXjgf/C3H1uL4Xexlyelv5oEmpZ7SQsxQqhESC6lP91tvA5w5N2exu9EitVnu7xGHUx9hQ1TbfOGZohbXczU3xhaJaNh+EL463JEoux/GOkVJnhRJH4egvdI5s=2019-10-03T09:44:18+03:00J03XIAgLz6aH0GAETXqeC1Gn/Nk=C=LT, L=Vilnius, O=Lietuvos bankas, OU=MSD, CN=LB-LITAS-CA109513179832538871370210ancestor-or-self::dsig:SignatureValueMIICZwYJKoZIhvcNAQcCoIICWDCCAlQCAQMxCzAJBgUrDgMCGgUAMGkGCyqGSIb3DQEJEAEEoFoEWDBWAgEBBgYEAM4PAQEwITAJBgUrDgMCGgUABBTyZdUrqlqkyxmuA+YSk10FZRVwNAIQCKK3NVOMUZlIPeEzgl0K+RgPMjAxOTEwMDMwNjQ0MjBaMAMCAQExggHVMIIB0QIBATBrMF0xCzAJBgNVBAYTAkxUMRAwDgYDVQQHEwdWaWxuaXVzMRgwFgYDVQQKEw9MaWV0dXZvcyBiYW5rYXMxDDAKBgNVBAsTA01TRDEUMBIGA1UEAxMLTEItTElUQVMtQ0ECCkmb8KUAAAAAAPYwCQYFKw4DAhoFAKBBMBoGCSqGSIb3DQEJAzENBgsqhkiG9w0BCRABBDAjBgkqhkiG9w0BCQQxFgQULELyJSS6MUN4GuzDv4IogBH98FIwDQYJKoZIhvcNAQEBBQAEggEAjYmT2yxBGwlhQS6lFvCRfHznqJQsXTWvj+ylt1RkyOL5vxHPLSnZgFDXMB409pN3qRrFbOmWN1ic5xmVy4pg+Zpgjm8OAROUVAsu2Up58+Gn4jZeCMTpikOx+RpnCzfZqVOrqZBEDpZHYVv945TGxVYvY0VU9VUXWILwv4j2/Fv2UibmdWSABP7MA3EwbzeKMt0ZBuhnv2wz/Si15qbwFUdVDNqM/bJaCDYyrBDgXJH1rpkcfLPRn/ffGNsS52i5nMXCuca+JhjXoMsI/JW+t7YzxLEq9N0xffeupVG4OlS7cJsNQnFilYDjiyoKcfnHtVclyh4wyE049cSXptOC+A==
diff --git a/testdata/bbauth-metadata.xml b/testdata/bbauth-metadata.xml
old mode 100644
new mode 100755
diff --git a/testdata/invalid-signature-changed content.xml b/testdata/invalid-signature-changed content.xml
old mode 100644
new mode 100755
diff --git a/testdata/invalid-signature-non-existing-reference.xml b/testdata/invalid-signature-non-existing-reference.xml
old mode 100644
new mode 100755
diff --git a/testdata/invalid-signature-signature-value.xml b/testdata/invalid-signature-signature-value.xml
old mode 100644
new mode 100755
diff --git a/testdata/nosignature-custom-reference-id-attribute.xml b/testdata/nosignature-custom-reference-id-attribute.xml
old mode 100644
new mode 100755
diff --git a/testdata/nosignature.xml b/testdata/nosignature.xml
old mode 100644
new mode 100755
diff --git a/testdata/own/minimal_signed.xml b/testdata/own/minimal_signed.xml
new file mode 100755
index 0000000..5a42040
--- /dev/null
+++ b/testdata/own/minimal_signed.xml
@@ -0,0 +1,54 @@
+
+
+
+
+
+
+
+
+
+
+ 3qPQF56HYcF7PZ//TPc3tSiN9PCV254ycN83zciMqHo=
+
+
+
+
+
+
+ izo8g0o8ks1271nYYzovz9+DTU/W+yXL5F3hogsfZXg=
+
+
+ MzKzMy9y7nTN5lHkPKaJPVIISf5XRzvmq7RoaJtpA7VWR5lctBVeO+c8EXPlfad5gKy3V/bzAtCgNcBbGoiVHu1giJd3Bo+NLt0sXHaXebLSbHnI0g/AvKD2GEGgGqrdviLrBNck9OV4tVZYtkV42kSXaZoTAEUxWNLZ01yhVZJrgn65XrcLmhZKViCtGS4JRvbA7r7ER6KVb6k3aP0VPFargRx+mBKTlv5jIK7Lkn0lBkN9IwvyVt+Xl2/A2PHVzWXMLEh09D64W4O/NyInWuZyUqGiJXi3AhhGUXTKm1Z0X+Sq2geLuHEDZ+WBgSm2f3gHZkbYK5ls6sAdjGZgFA==
+ MIID2TCCAsGgAwIBAgIUEEnabJkXk0Cflcvmaw/J1dZqct8wDQYJKoZIhvcNAQELBQAwTjELMAkGA1UEBhMCVVMxEzARBgNVBAgMCkNhbGlmb3JuaWExEjAQBgNVBAcMCVRoZSBDbG91ZDEWMBQGA1UECgwNTXkgQ29tcGFueSBDQTAeFw0yMTExMjExMjQxMThaFw0yMjAzMjExMjQxMThaMIGNMQswCQYDVQQGEwJMVDESMBAGA1UECAwJTGl0aHVhbmlhMRIwEAYDVQQHDAlUaGUgQ2xvdWQxDTALBgNVBAoMBERlbW8xFTATBgNVBAMMDFNpZ25pbmcgY2VydDEwMC4GCgmSJomT8ixkARkWIGxvY2FsaG9zdC9lbWFpbEFkZHJlc3M9c2RAc2RmLmZmMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAwLBaiVFQg8xTW7NTcCre7hy2yEKgZmGeESsUMOufOd0Hp7xraYp/CuTj8fptHHqFcIqdI520Br17WESWe5WM5XxkA/eWAmN3hBbi5lUM5b9w/KwAkq8RHBZaq+vHwVAFMnlZMBWRz2R5PcQ2NyJj0YBgImJImhm7KllkKvuLLXNqjYYp7S4n/lXbPHOYA28PeJlPLYe4Wbpe18aGoGRaag9PK4oR4tX1rHjtcAWf8u7NmJX56/6xRl15qsMngTNJKu6mxMDd5i9X1xDNRzVN7uTlK9CBqmq+aflDLCnSpiZX/W3AazOpJm5Cm1f8vqOX8XnJ6DIWUREnZ/IU2CT7dwIDAQABo28wbTAMBgNVHRMBAf8EAjAAMB0GA1UdDgQWBBRd2sKVueUBgVKqiiwOOZ93E7Kw+jALBgNVHQ8EBAMCBeAwMQYDVR0lBCowKAYIKwYBBQUHAwIGCCsGAQUFBwMJBggrBgEFBQcDAwYIKwYBBQUHAwgwDQYJKoZIhvcNAQELBQADggEBAIKoE6AXe2akNOPeVvGWc4BaATm8M/E+UqjkD0j6hPlQJeJBEPZfFYxqSG1egK6LkvA7cZei1gFqrKbHwyrnda8fiLB+0TkgQKJGU427GFSSaAHtqY1nSsXcDTTQCsjvIY9BzYzcfdD7grYzvBdCuhwU7Wtki6RX6cVKK08HlO+k+CN8/bo0SAns8STGluFLP7RwR6U0bHXOGQJoFetJZ2+jj1NfuCkolrV1qWzhdznxvrD9j/BAaPi8kiMHbApF2NpQkP/vLWqYVPp0z4WEZ2UpFUWPAfVXOnRDSFpTGEdmeXdbPsR2/QsM41WZqSe6zDv+zISMpwgmuKbkhthK7gc=
+
+
+
\ No newline at end of file
diff --git a/testdata/own/minimal_signed_RSAKeyValue.xml b/testdata/own/minimal_signed_RSAKeyValue.xml
new file mode 100755
index 0000000..5f35cc6
--- /dev/null
+++ b/testdata/own/minimal_signed_RSAKeyValue.xml
@@ -0,0 +1,5 @@
+
+
+ what not
+
+IkKICQEn7PJZJQzT/v99xf7kXg8C/TxVoF8vcX0pk3Q=YCvhYGsMf37qNi7lejSOy/FvP+DkGXjjolIqpUpAppg=mRAGkoy1Hy17ZatGEQjbPdohceYImv9S+KTHe2W7B8JCquEZ+P3zYQnZP4VTPMnhJCn+8VyO5HhYa52QDiwzErRxLSOaNzmdn2KZBccmb/ZvKws5/8LkUGbUaFJqwiQr7/qsLyuvfjXZ9qEmMjxg3OMZNJFt/f9l1U11ucTvoY5ZDCtUFYiztvAWgucjrboeYD/51q8+5RpaasYMt9WEyh+UYR1jyDSIHoq14gl4K0FbzCHBV7ufOz6hTPTlOY8revCl9/PBh6QMG7nlzAVaeZNh+m3OG0IVfCSWvBNHLuNxWdWAF6crawzYZi+nRpWXzZ/tA22S4q3ROh0NgtYYGw==wLBaiVFQg8xTW7NTcCre7hy2yEKgZmGeESsUMOufOd0Hp7xraYp/CuTj8fptHHqFcIqdI520Br17WESWe5WM5XxkA/eWAmN3hBbi5lUM5b9w/KwAkq8RHBZaq+vHwVAFMnlZMBWRz2R5PcQ2NyJj0YBgImJImhm7KllkKvuLLXNqjYYp7S4n/lXbPHOYA28PeJlPLYe4Wbpe18aGoGRaag9PK4oR4tX1rHjtcAWf8u7NmJX56/6xRl15qsMngTNJKu6mxMDd5i9X1xDNRzVN7uTlK9CBqmq+aflDLCnSpiZX/W3AazOpJm5Cm1f8vqOX8XnJ6DIWUREnZ/IU2CT7dw==AQAB
\ No newline at end of file
diff --git a/testdata/own/pacs008_signed.xml b/testdata/own/pacs008_signed.xml
new file mode 100755
index 0000000..89e869e
--- /dev/null
+++ b/testdata/own/pacs008_signed.xml
@@ -0,0 +1,55 @@
+
+
+
+
+ 3408f3caf90d4fceafac322ab3f3f870
+ 2021-04-20T18:21:43
+ 1
+ 170.06
+ 2021-04-20
+
+ INGA
+
+ INTRAGROUP
+
+
+
+
+
+ NOTPROVIDED
+ 5bab4907dc98463a8b305f68812bad42
+
+ 170.06
+ SLEV
+
+ Tadeush Blindewski
+
+
+
+ LT372140000000000001
+
+
+
+
+ AGBLLT2XXXX
+
+
+
+
+ CBSBLT26XXX
+
+
+
+ Иван Царевич
+
+
+
+ LT497181600000000002
+
+
+
+ А, уж ка тас паведимас? Вистэк ачу.
+
+
+
+R/ueo9oM8J+1P8QWh4W6FS/zvqKbnyWDh9ndALABIMI=6ObG98/6Hdcv2QRp/RGmV1iI6xkkgzOpYVJFmbKYK3A=fiiMKGjmr7oUTvuEHeKdh50Oc3pu8MS75XMYVUHBE1nI/vOW5xF7Xvl1/hqU87R178q2NzmEBl9gOnl4d3Q3xsE4wFGV7xMqfoYrHIj3yrqiAzd88qnAd3QPJVuKLLwhT7qaadqle0qP+gmytIX5nFFFCuwgWgk47LCWHSa8pkbgxZsJDBTtox8OPcGnUFSkJMwKeld8oiicjMKXZDYqjUdASeI60bFWcLi8PNbDrnHye8LlrqGARVCSPpqQUdH3QtWO3YLLM2bq8oSCWxAcL604y2B29yDroSbvpj/w5vMwy/K09KMA8OvE0DaWlAA/xUqq+ajqzYCDOC7IJOKVow==MIID2TCCAsGgAwIBAgIUEEnabJkXk0Cflcvmaw/J1dZqct8wDQYJKoZIhvcNAQELBQAwTjELMAkGA1UEBhMCVVMxEzARBgNVBAgMCkNhbGlmb3JuaWExEjAQBgNVBAcMCVRoZSBDbG91ZDEWMBQGA1UECgwNTXkgQ29tcGFueSBDQTAeFw0yMTExMjExMjQxMThaFw0yMjAzMjExMjQxMThaMIGNMQswCQYDVQQGEwJMVDESMBAGA1UECAwJTGl0aHVhbmlhMRIwEAYDVQQHDAlUaGUgQ2xvdWQxDTALBgNVBAoMBERlbW8xFTATBgNVBAMMDFNpZ25pbmcgY2VydDEwMC4GCgmSJomT8ixkARkWIGxvY2FsaG9zdC9lbWFpbEFkZHJlc3M9c2RAc2RmLmZmMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAwLBaiVFQg8xTW7NTcCre7hy2yEKgZmGeESsUMOufOd0Hp7xraYp/CuTj8fptHHqFcIqdI520Br17WESWe5WM5XxkA/eWAmN3hBbi5lUM5b9w/KwAkq8RHBZaq+vHwVAFMnlZMBWRz2R5PcQ2NyJj0YBgImJImhm7KllkKvuLLXNqjYYp7S4n/lXbPHOYA28PeJlPLYe4Wbpe18aGoGRaag9PK4oR4tX1rHjtcAWf8u7NmJX56/6xRl15qsMngTNJKu6mxMDd5i9X1xDNRzVN7uTlK9CBqmq+aflDLCnSpiZX/W3AazOpJm5Cm1f8vqOX8XnJ6DIWUREnZ/IU2CT7dwIDAQABo28wbTAMBgNVHRMBAf8EAjAAMB0GA1UdDgQWBBRd2sKVueUBgVKqiiwOOZ93E7Kw+jALBgNVHQ8EBAMCBeAwMQYDVR0lBCowKAYIKwYBBQUHAwIGCCsGAQUFBwMJBggrBgEFBQcDAwYIKwYBBQUHAwgwDQYJKoZIhvcNAQELBQADggEBAIKoE6AXe2akNOPeVvGWc4BaATm8M/E+UqjkD0j6hPlQJeJBEPZfFYxqSG1egK6LkvA7cZei1gFqrKbHwyrnda8fiLB+0TkgQKJGU427GFSSaAHtqY1nSsXcDTTQCsjvIY9BzYzcfdD7grYzvBdCuhwU7Wtki6RX6cVKK08HlO+k+CN8/bo0SAns8STGluFLP7RwR6U0bHXOGQJoFetJZ2+jj1NfuCkolrV1qWzhdznxvrD9j/BAaPi8kiMHbApF2NpQkP/vLWqYVPp0z4WEZ2UpFUWPAfVXOnRDSFpTGEdmeXdbPsR2/QsM41WZqSe6zDv+zISMpwgmuKbkhthK7gc=
\ No newline at end of file
diff --git a/testdata/rootxmlns.crt b/testdata/rootxmlns.crt
old mode 100644
new mode 100755
diff --git a/testdata/rootxmlns.xml b/testdata/rootxmlns.xml
old mode 100644
new mode 100755
diff --git a/testdata/rsa.crt b/testdata/rsa.crt
old mode 100644
new mode 100755
diff --git a/testdata/rsa.key b/testdata/rsa.key
old mode 100644
new mode 100755
diff --git a/testdata/saml-external-ns.xml b/testdata/saml-external-ns.xml
old mode 100644
new mode 100755
diff --git a/testdata/signature-with-inclusivenamespaces.xml b/testdata/signature-with-inclusivenamespaces.xml
old mode 100644
new mode 100755
diff --git a/testdata/valid-saml.xml b/testdata/valid-saml.xml
old mode 100644
new mode 100755
diff --git a/testdata/windows-store-signature.xml b/testdata/windows-store-signature.xml
old mode 100644
new mode 100755
diff --git a/testdata/wsfed-metadata.xml b/testdata/wsfed-metadata.xml
old mode 100644
new mode 100755
diff --git a/validator.go b/validator.go
old mode 100644
new mode 100755
index eb13580..4eb9d38
--- a/validator.go
+++ b/validator.go
@@ -1,19 +1,25 @@
package signedxml
import (
+ "crypto"
+ "crypto/rsa"
"crypto/x509"
"encoding/base64"
"errors"
"fmt"
"log"
+ "math/big"
+ "regexp"
+ "time"
"github.com/beevik/etree"
)
// Validator provides options for verifying a signed XML document
type Validator struct {
- Certificates []x509.Certificate
- signingCert x509.Certificate
+ Certificates []x509.Certificate
+ RSAPublicKeys []*rsa.PublicKey
+ signingCert x509.Certificate
signatureData
}
@@ -54,6 +60,7 @@ func (v *Validator) SigningCert() x509.Certificate {
// Deprecated: Use ValidateReferences instead
func (v *Validator) Validate() error {
_, err := v.ValidateReferences()
+ // TODO: validate certificate digest at the SignedProperties - maybe elsewhere?
return err
}
@@ -70,12 +77,14 @@ func (v *Validator) ValidateReferences() ([]string, error) {
return nil, err
}
+ // referenced is array of cannonicalized reference elements whose digest is calculated
+ // and compared to original ones. If err is nil, then all digests of references match.
referenced, err := v.validateReferences()
if err != nil {
return nil, err
}
- var ref []string
+ var ref []string // referenced elements -> ref string array
for _, doc := range referenced {
docStr, err := doc.WriteToString()
if err != nil {
@@ -84,6 +93,8 @@ func (v *Validator) ValidateReferences() ([]string, error) {
ref = append(ref, docStr)
}
+ // checks SignatureValue using x509.Certificate{}.CheckSignature function.
+ // Function params - canonicalized SignedInfo block, and the SignatureValue
err = v.validateSignature()
return ref, err
}
@@ -116,20 +127,65 @@ func (v *Validator) validateReferences() (referenced []*etree.Document, err erro
references := v.signedInfo.FindElements("./Reference")
for _, ref := range references {
doc := v.xml.Copy()
- transforms := ref.SelectElement("Transforms")
- for _, transform := range transforms.SelectElements("Transform") {
- doc, err = processTransform(transform, doc)
+ // transforms := ref.SelectElement("Transforms")
+ // for _, transform := range transforms.SelectElements("Transform") {
+ // doc, err = processTransform(transform, doc)
+ // if err != nil {
+ // return nil, err
+ // }
+ // }
+
+ // doc, err = v.getReferencedXML(ref, doc)
+ // if err != nil {
+ // return nil, err
+ // }
+
+ // MOD: 1. change order: 1st find the ref, then 3. transform
+ // 2. if not root doc, add namespaces
+ targetDoc, err := v.getReferencedXML(ref, doc)
+ if err != nil {
+ return nil, err
+ }
+
+ // 2. copy relevant namespaces if the targetDoc is not the root document
+ if targetDoc.Root().Tag != v.xml.Root().Tag {
+
+ // if targetDoc element is not root (i.e, root sub-tag or child) being "digested",
+ // then populate with relevant namespaces
+ err = PopulateElementWithNameSpaces(targetDoc.Root(), v.xml.Copy())
if err != nil {
return nil, err
}
}
- doc, err = v.getReferencedXML(ref, doc)
+ // 3. do the transforms
+ transforms := ref.SelectElement("Transforms")
+ if transforms != nil {
+ for _, transform := range transforms.SelectElements("Transform") {
+ targetDoc, err = processTransform(transform, targetDoc)
+ if err != nil {
+ return nil, err
+ }
+ }
+ }
+
+ // 4. canonicalization, defined at the signature level is mandatory for each
+ // reference before calculating the hash. This is to avoid situations when canonicalization
+ // is not explicitly defined in the of the Reference (it's implied).
+ // source: https://www.di-mgt.com.au/xmldsig2.html
+ targetDocStr, err := targetDoc.WriteToString()
if err != nil {
return nil, err
}
+ targetDocStr, err = v.canonAlgorithm.Process(targetDocStr, "")
+ if err != nil {
+ return nil, err
+ }
+ targetDoc2 := etree.NewDocument()
+ targetDoc2.ReadFromString(targetDocStr)
- referenced = append(referenced, doc)
+ // continue with the old code:
+ referenced = append(referenced, targetDoc2)
digestValueElement := ref.SelectElement("DigestValue")
if digestValueElement == nil {
@@ -137,7 +193,8 @@ func (v *Validator) validateReferences() (referenced []*etree.Document, err erro
}
digestValue := digestValueElement.Text()
- calculatedValue, err := calculateHash(ref, doc)
+ // calculatedValue, err := calculateHash(ref, doc)
+ calculatedValue, err := CalculateHashFromRef(ref, targetDoc2)
if err != nil {
return nil, err
}
@@ -163,21 +220,70 @@ func (v *Validator) validateSignature() error {
return err
}
- b64, err := base64.StdEncoding.DecodeString(v.sigValue)
+ // debug
+ // fn := strconv.FormatInt(time.Now().UnixNano(), 10) + ".xml" // unix-time based filename
+ // f, err := os.Create(fn)
+ // if err != nil {
+ // panic(err)
+ // }
+ // defer f.Close()
+ // _, err = f.Write([]byte(canonSignedInfo))
+ // if err != nil {
+ // panic(err)
+ // }
+
+ signatureBytes, err := base64.StdEncoding.DecodeString(v.sigValue)
if err != nil {
return err
}
- sig := []byte(b64)
+ // sig := []byte(b64) // useless double conversion from bytes to bytes
v.signingCert = x509.Certificate{}
for _, cert := range v.Certificates {
- err := cert.CheckSignature(v.sigAlgorithm, []byte(canonSignedInfo), sig)
+ err := cert.CheckSignature(v.sigAlgorithm, []byte(canonSignedInfo), signatureBytes)
if err == nil {
v.signingCert = cert
return nil
}
}
+ // MOD: added RSA PublicKey checking of the signature
+ if v.RSAPublicKeys != nil {
+ signingAlgorithm, ok := signingAlgorithms[v.sigAlgorithm]
+ if !ok {
+ return errors.New("signedxml: unsupported algorithm")
+ }
+ hasher := signingAlgorithm.hash.New()
+ hasher.Write([]byte(canonSignedInfo))
+ digest := hasher.Sum(nil)
+ // fmt.Println(base64.StdEncoding.EncodeToString(digest)) // debug
+
+ for _, pubKey := range v.RSAPublicKeys {
+ // err := rsa.VerifyPKCnotAfterv15(pubKey, crypto.Hash(v.sigAlgorithm), digest, signatureBytes)
+
+ var cryptoHashId crypto.Hash
+ switch v.sigAlgorithm.String() {
+ case "MD5-RSA":
+ cryptoHashId = crypto.MD5
+ case "SHA1-RSA":
+ cryptoHashId = crypto.SHA1
+ case "SHA256-RSA", "SHA256-RSAPSS":
+ cryptoHashId = crypto.SHA256
+ case "SHA384-RSA", "SHA384-RSAPSS":
+ cryptoHashId = crypto.SHA384
+ case "SHA512-RSA", "SHA512-RSAPSS":
+ cryptoHashId = crypto.SHA512
+ default:
+ return errors.New("unknown signature hash type")
+ }
+
+ err := rsa.VerifyPKCS1v15(pubKey, cryptoHashId, digest, signatureBytes)
+ if err == nil {
+ return nil // signature validated
+ }
+ }
+ }
+
return errors.New("signedxml: Calculated signature does not match the " +
"SignatureValue provided")
}
@@ -186,20 +292,193 @@ func (v *Validator) loadCertificates() error {
// If v.Certificates is already populated, then the client has already set it
// to the desired cert. Otherwise, let's pull the public keys from the XML
if len(v.Certificates) < 1 {
- keydata := v.xml.FindElements(".//X509Certificate")
- for _, key := range keydata {
- cert, err := getCertFromPEMString(key.Text())
- if err != nil {
- log.Printf("signedxml: Unable to load certificate: (%s). "+
- "Looking for another cert.", err)
- } else {
+ switch {
+ case len(v.xml.FindElements(".//X509Certificate")) >= 1:
+ keydata := v.xml.FindElements(".//X509Certificate")
+ for _, key := range keydata {
+ cert, err := LoadCertFromPEMString(key.Text(), "CERTIFICATE")
+ if err != nil {
+ log.Printf("signedxml: Unable to load certificate: (%s). "+
+ "Looking for another cert.", err)
+ continue // don't append current cert: it will be nil due to error
+ }
+
+ // if certificate digest and digest method are present, validate the certificate
+ // TODO: what if there are multiple certificates in the ?
+ var certDigest, digestMethodURI string
+ if el := v.xml.FindElement(".//CertDigest/DigestMethod"); el != nil {
+ digestMethodURI = el.SelectAttrValue("Algorithm", "")
+ }
+ if el := v.xml.FindElement(".//CertDigest/DigestValue"); el != nil {
+ certDigest = el.Text()
+ }
+ err = ValidateCertificate(cert, certDigest, digestMethodURI, "", "")
+ if err != nil {
+ log.Printf("signedxml: certificate validation failed: (%s). "+
+ "Looking for another cert.", err)
+ continue // don't append current cert: it will be nil due to error
+ }
+
v.Certificates = append(v.Certificates, *cert)
}
+
+ case len(v.xml.FindElements(".//RSAKeyValue")) >= 1:
+ keydata := v.xml.FindElements(".//RSAKeyValue")
+ for _, key := range keydata {
+ modulus := key.SelectElement("Modulus")
+ if modulus == nil {
+ log.Printf("signedxml: RSA Modulus not found, cannot load certificate. Looking for another cert.")
+ continue
+ }
+ modulusBytes, err := base64.StdEncoding.DecodeString(modulus.Text())
+ if err != nil {
+ log.Printf("signedxml: can't b64 decode RSA modulus (%s). "+
+ "Looking for another cert.", err)
+ continue
+ }
+ exponent := key.SelectElement("Exponent")
+ if exponent == nil {
+ log.Printf("signedxml: RSA Exponent not found, cannot load certificate. Looking for another cert.")
+ continue
+ }
+
+ // source: https://stackoverflow.com/questions/41127019/go-language-convert-modulus-exponent-to-x-509-certificate
+ e := 65537
+ // The default exponent is usually 65537, so just compare the
+ // base64 for [1,0,1] or [0,1,0,1]
+ if exponent.Text() != "AQAB" && exponent.Text() != "AAEAAQ" {
+ // still need to decode the big-endian int
+ log.Printf("signedxml: unusual RSA exponent ('%s', base64). "+
+ "still need to decode it, looking for another cert.", exponent.Text())
+ continue
+ }
+ pubKey := &rsa.PublicKey{
+ N: new(big.Int).SetBytes(modulusBytes),
+ E: e,
+ }
+ v.RSAPublicKeys = append(v.RSAPublicKeys, pubKey)
+
+ // TODO: not sure if certificate validation is possible with RSA key
+
+ // pubKeyDERBytes := pem.EncodeToMemory(&pem.Block{
+ // Type: "RSA PUBLIC KEY",
+ // Bytes: x509.MarshalPKCnotAfterPublicKey(pub),
+ // })
+ // cert, err := x509.ParseCertificate(pubKeyDERBytes)
+
+ // exponentBytes, err := base64.StdEncoding.DecodeString(exponent.Text())
+ // if err != nil {
+ // log.Printf("signedxml: can't b64 decode RSA exponent (%s). "+
+ // "Looking for another cert.", err)
+ // continue
+ // }
+
+ // // conversion to BigEndian
+ // if len(exponentBytes) < 4 {
+ // ndata := make([]byte, 4)
+ // copy(ndata[4-len(exponentBytes):], exponentBytes)
+ // exponentBytes = ndata
+ // }
+
+ // pubKey := &rsa.PublicKey{
+ // N: new(big.Int).SetBytes(modulusBytes),
+ // // E: int(binary.BigEndian.Uint32(exponentBytes[:])),
+ // E: 65537,
+ // }
+
+ }
}
}
- if len(v.Certificates) < 1 {
- return errors.New("signedxml: a certificate is required, but was not found")
+ if len(v.Certificates) < 1 && v.RSAPublicKeys == nil {
+ return errors.New("signedxml: a X509 certificate or a RSA public key is required, but was not found")
}
return nil
}
+
+func (v *Validator) SetValidationCertFromPEMString(certPEM string) error {
+ cert, err := LoadCertFromPEMString(certPEM, "CERTIFICATE")
+ if err != nil {
+ return fmt.Errorf("signedxml: Unable to load certificate: (%s). ", err)
+ }
+ v.Certificates = append(v.Certificates, *cert)
+ return nil
+}
+
+func (v *Validator) SetValidationCert(cert *x509.Certificate) {
+ v.Certificates = append(v.Certificates, *cert)
+}
+
+// Validates certificate:
+// 1. checks if it hasn't expired,
+// 2. calculates certificate hash digest and compares to supplied certificate digest value.
+// Params 'notBefore', 'notAfter' are optional, just for setting validity dates separately, else
+// X509.Certificate container equivalent values are used
+func ValidateCertificate(cert *x509.Certificate, certDigest, digestMethodURI, notBefore, notAfter string) (err error) {
+
+ // setup of custom NotBefore and NotAfter dates
+ if notBefore != "" && notAfter != "" {
+
+ var layout string
+ var t0, t1 time.Time
+
+ r1 := regexp.MustCompile(`^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}Z$`)
+ r3 := regexp.MustCompile(`^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}[+-]\d{2}:\d{2}$`)
+ r4 := regexp.MustCompile(`^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}$`)
+ r5 := regexp.MustCompile(`^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}.\d{1,9}$`)
+ r6 := regexp.MustCompile(`^\d{4}-\d{2}-\d{2}$`)
+
+ switch {
+ case r1.Match([]byte(notBefore)) || r3.Match([]byte(notBefore)):
+ layout = time.RFC3339 // "2006-01-02T15:04:05+HH:MM"
+ case r4.Match([]byte(notBefore)) || r5.Match([]byte(notBefore)):
+ layout = "2006-01-02T15:04:05" // local tz
+ case r6.Match([]byte(notBefore)):
+ layout = "2006-01-02" // local tz
+ }
+ if r4.Match([]byte(notBefore)) || r5.Match([]byte(notBefore)) || r6.Match([]byte(notBefore)) {
+ t0, err = time.ParseInLocation(layout, notBefore, time.UTC)
+ } else if r1.Match([]byte(notBefore)) || r3.Match([]byte(notBefore)) {
+ t0, err = time.Parse(layout, notBefore)
+ }
+ if err != nil {
+ return fmt.Errorf("error parsing date string %s, error: %s", t0, err)
+ }
+ // assume that t1 is in the same format as t0
+ if r4.Match([]byte(notAfter)) || r5.Match([]byte(notAfter)) || r6.Match([]byte(notAfter)) {
+ t1, err = time.ParseInLocation(layout, notAfter, time.UTC)
+ } else if r1.Match([]byte(notAfter)) || r3.Match([]byte(notAfter)) {
+ t1, err = time.Parse(layout, notAfter)
+ }
+ if err != nil {
+ return fmt.Errorf("error parsing date string %s, error: %s", t1, err)
+ }
+
+ cert.NotBefore = t0
+ cert.NotAfter = t1
+ }
+
+ // checking if certificate expired
+ if time.Now().Before(cert.NotBefore) {
+ return fmt.Errorf("certificate is not valid until %s; now is %s",
+ cert.NotBefore.UTC().Format("2006-01-02T15:04:05Z"), time.Now().UTC().Format("2006-01-02T15:04:05Z"))
+ }
+ if time.Now().After(cert.NotAfter) {
+ return fmt.Errorf("certificate has expired in %s; now is %s",
+ cert.NotAfter.UTC().Format("2006-01-02T15:04:05Z"), time.Now().UTC().Format("2006-01-02T15:04:05Z"))
+ }
+
+ // calculate certificate hash
+ certDigestB64, err := CalculateHash(cert.Raw, digestMethodURI)
+ if err != nil {
+ return err
+ }
+
+ // check if hash matches
+ if certDigest != certDigestB64 {
+ return fmt.Errorf("certificate hash digest mismatch: xml uses certificate hash "+
+ "digest '%s', while cert digest in db is '%s'", certDigest, certDigestB64)
+ }
+
+ return
+}