Skip to content
This repository has been archived by the owner on Jun 6, 2023. It is now read-only.

Commit

Permalink
Use signedxml to create and validate signatures
Browse files Browse the repository at this point in the history
  • Loading branch information
Calpicow committed Dec 16, 2016
1 parent 92df338 commit 0c280ee
Show file tree
Hide file tree
Showing 2 changed files with 45 additions and 54 deletions.
1 change: 1 addition & 0 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -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)"
Expand Down
98 changes: 44 additions & 54 deletions xmlsec.go
Original file line number Diff line number Diff line change
@@ -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("<?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())
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
}

0 comments on commit 0c280ee

Please sign in to comment.