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
 }