This repository has been archived by the owner on Jun 6, 2023. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 82
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Use signedxml to create and validate signatures
- Loading branch information
Showing
2 changed files
with
45 additions
and
54 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 | ||
} |