From 0c280eec74dc3adf321b64d21b35cbc14a4cc2fa Mon Sep 17 00:00:00 2001 From: Phu Kieu Date: Thu, 8 Dec 2016 16:11:17 -0800 Subject: [PATCH] Use signedxml to create and validate signatures --- Makefile | 1 + xmlsec.go | 98 +++++++++++++++++++++++++------------------------------ 2 files changed, 45 insertions(+), 54 deletions(-) diff --git a/Makefile b/Makefile index 5469518..db346e2 100644 --- a/Makefile +++ b/Makefile @@ -13,6 +13,7 @@ init: go get github.com/nu7hatch/gouuid go get github.com/kardianos/osext go get github.com/stretchr/testify/assert + go get github.com/ma314smith/signedxml vet: init @echo "$(OK_COLOR)==> Go Vetting$(NO_COLOR)" diff --git a/xmlsec.go b/xmlsec.go index 484a940..16d9cb0 100644 --- a/xmlsec.go +++ b/xmlsec.go @@ -1,103 +1,93 @@ package saml import ( + "crypto/x509" + "encoding/pem" "errors" "io/ioutil" - "os" - "os/exec" - "strings" -) -const ( - xmlResponseID = "urn:oasis:names:tc:SAML:2.0:protocol:Response" - xmlRequestID = "urn:oasis:names:tc:SAML:2.0:protocol:AuthnRequest" + "github.com/ma314smith/signedxml" ) // SignRequest sign a SAML 2.0 AuthnRequest -// `privateKeyPath` must be a path on the filesystem, xmlsec1 is run out of process -// through `exec` +// `privateKeyPath` must be a path on the filesystem func SignRequest(xml string, privateKeyPath string) (string, error) { - return sign(xml, privateKeyPath, xmlRequestID) + return sign(xml, privateKeyPath) } // SignResponse sign a SAML 2.0 Response -// `privateKeyPath` must be a path on the filesystem, xmlsec1 is run out of process -// through `exec` +// `privateKeyPath` must be a path on the filesystem func SignResponse(xml string, privateKeyPath string) (string, error) { - return sign(xml, privateKeyPath, xmlResponseID) + return sign(xml, privateKeyPath) } -func sign(xml string, privateKeyPath string, id string) (string, error) { - - samlXmlsecInput, err := ioutil.TempFile(os.TempDir(), "tmpgs") +func sign(xml string, privateKeyPath string) (string, error) { + pemString, err := ioutil.ReadFile(privateKeyPath) if err != nil { return "", err } - defer deleteTempFile(samlXmlsecInput.Name()) - samlXmlsecInput.WriteString("\n") - samlXmlsecInput.WriteString(xml) - samlXmlsecInput.Close() - samlXmlsecOutput, err := ioutil.TempFile(os.TempDir(), "tmpgs") + pemBlock, _ := pem.Decode([]byte(pemString)) + if pemBlock == nil { + return "", errors.New("Count not parse private key") + } + + key, err := x509.ParsePKCS1PrivateKey(pemBlock.Bytes) if err != nil { return "", err } - defer deleteTempFile(samlXmlsecOutput.Name()) - samlXmlsecOutput.Close() - - // fmt.Println("xmlsec1", "--sign", "--privkey-pem", privateKeyPath, - // "--id-attr:ID", id, - // "--output", samlXmlsecOutput.Name(), samlXmlsecInput.Name()) - output, err := exec.Command("xmlsec1", "--sign", "--privkey-pem", privateKeyPath, - "--id-attr:ID", id, - "--output", samlXmlsecOutput.Name(), samlXmlsecInput.Name()).CombinedOutput() + + signer, err := signedxml.NewSigner(xml) if err != nil { - return "", errors.New(err.Error() + " : " + string(output)) + return "", err } - samlSignedRequest, err := ioutil.ReadFile(samlXmlsecOutput.Name()) + samlSignedRequestXML, err := signer.Sign(key) if err != nil { return "", err } - samlSignedRequestXML := strings.Trim(string(samlSignedRequest), "\n") + return samlSignedRequestXML, nil } // VerifyResponseSignature verify signature of a SAML 2.0 Response document -// `publicCertPath` must be a path on the filesystem, xmlsec1 is run out of process -// through `exec` +// `publicCertPath` must be a path on the filesystem func VerifyResponseSignature(xml string, publicCertPath string) error { - return verify(xml, publicCertPath, xmlResponseID) + return verify(xml, publicCertPath) } // VerifyRequestSignature verify signature of a SAML 2.0 AuthnRequest document -// `publicCertPath` must be a path on the filesystem, xmlsec1 is run out of process -// through `exec` +// `publicCertPath` must be a path on the filesystem func VerifyRequestSignature(xml string, publicCertPath string) error { - return verify(xml, publicCertPath, xmlRequestID) + return verify(xml, publicCertPath) } -func verify(xml string, publicCertPath string, id string) error { - //Write saml to - samlXmlsecInput, err := ioutil.TempFile(os.TempDir(), "tmpgs") +func verify(xml string, publicCertPath string) error { + pemString, err := ioutil.ReadFile(publicCertPath) if err != nil { return err } - samlXmlsecInput.WriteString(xml) - samlXmlsecInput.Close() - defer deleteTempFile(samlXmlsecInput.Name()) + pemBlock, _ := pem.Decode([]byte(pemString)) + if pemBlock == nil { + return errors.New("Could not parse certificate") + } - //fmt.Println("xmlsec1", "--verify", "--pubkey-cert-pem", publicCertPath, "--id-attr:ID", id, samlXmlsecInput.Name()) - _, err = exec.Command("xmlsec1", "--verify", "--pubkey-cert-pem", publicCertPath, "--id-attr:ID", id, samlXmlsecInput.Name()).CombinedOutput() + cert, err := x509.ParseCertificate(pemBlock.Bytes) if err != nil { - return errors.New("error verifing signature: " + err.Error()) + return err + } + + validator, err := signedxml.NewValidator(xml) + if err != nil { + return err } - return nil -} -// deleteTempFile remove a file and ignore error -// Intended to be called in a defer after the creation of a temp file to ensure cleanup -func deleteTempFile(filename string) { - _ = os.Remove(filename) + validator.Certificates = append(validator.Certificates, *cert) + + err = validator.Validate() + if err != nil { + return err + } + return nil }