From a70834e6341bb7ed6048f3f5db4854c1a71c15f3 Mon Sep 17 00:00:00 2001 From: Phu Kieu <pskieu@gmail.com> Date: Thu, 8 Dec 2016 16:11:17 -0800 Subject: [PATCH] Use signedxml to create and validate signatures --- Makefile | 1 + xmlsec.go | 85 +++++++++++++++++++++++++++++-------------------------- 2 files changed, 46 insertions(+), 40 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..198e657 100644 --- a/xmlsec.go +++ b/xmlsec.go @@ -1,11 +1,14 @@ package saml import ( + "crypto/x509" "errors" "io/ioutil" "os" - "os/exec" - "strings" + + "encoding/pem" + + "github.com/ma314smith/signedxml" ) const ( @@ -14,84 +17,86 @@ const ( ) // 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) } // 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) } func sign(xml string, privateKeyPath string, id string) (string, error) { - - samlXmlsecInput, err := ioutil.TempFile(os.TempDir(), "tmpgs") + pemString, err := ioutil.ReadFile(privateKeyPath) if err != nil { return "", err } - defer deleteTempFile(samlXmlsecInput.Name()) - samlXmlsecInput.WriteString("<?xml version='1.0' encoding='UTF-8'?>\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()) + signer.PrivateKey = key + + samlSignedRequestXML, err := signer.Sign() 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 + } + + pemBlock, _ := pem.Decode([]byte(pemString)) + if pemBlock == nil { + return errors.New("Could not parse certificate") + } + + cert, err := x509.ParseCertificate(pemBlock.Bytes) if err != nil { return err } - samlXmlsecInput.WriteString(xml) - samlXmlsecInput.Close() - defer deleteTempFile(samlXmlsecInput.Name()) + validator, err := signedxml.NewValidator(xml) + if err != nil { + return err + } - //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() + validator.Certificates = append(validator.Certificates, *cert) + + err = validator.Validate() if err != nil { - return errors.New("error verifing signature: " + err.Error()) + return err } return nil }