| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335 |
- // Package signedxml transforms and validates signedxml documents
- package signedxml
- import (
- "crypto"
- "crypto/x509"
- "encoding/base64"
- "encoding/pem"
- "errors"
- "fmt"
- "log"
- "os"
- "strings"
- "github.com/beevik/etree"
- )
- var logger = log.New(os.Stdout, "DEBUG-SIGNEDXML: ", log.Ldate|log.Ltime|log.Lshortfile)
- func init() {
- hashAlgorithms = map[string]crypto.Hash{
- "http://www.w3.org/2001/04/xmldsig-more#md5": crypto.MD5,
- "http://www.w3.org/2000/09/xmldsig#sha1": crypto.SHA1,
- "http://www.w3.org/2001/04/xmldsig-more#sha224": crypto.SHA224,
- "http://www.w3.org/2001/04/xmlenc#sha256": crypto.SHA256,
- "http://www.w3.org/2001/04/xmldsig-more#sha384": crypto.SHA384,
- "http://www.w3.org/2001/04/xmlenc#sha512": crypto.SHA512,
- "http://www.w3.org/2001/04/xmlenc#ripemd160": crypto.RIPEMD160,
- }
- signatureAlgorithms = map[string]x509.SignatureAlgorithm{
- "http://www.w3.org/2001/04/xmldsig-more#rsa-md2": x509.MD2WithRSA,
- "http://www.w3.org/2001/04/xmldsig-more#rsa-md5": x509.MD5WithRSA,
- "http://www.w3.org/2000/09/xmldsig#rsa-sha1": x509.SHA1WithRSA,
- "http://www.w3.org/2001/04/xmldsig-more#rsa-sha256": x509.SHA256WithRSA,
- "http://www.w3.org/2001/04/xmldsig-more#rsa-sha384": x509.SHA384WithRSA,
- "http://www.w3.org/2001/04/xmldsig-more#rsa-sha512": x509.SHA512WithRSA,
- "http://www.w3.org/2000/09/xmldsig#dsa-sha1": x509.DSAWithSHA1,
- "http://www.w3.org/2000/09/xmldsig#dsa-sha256": x509.DSAWithSHA256,
- "http://www.w3.org/2001/04/xmldsig-more#ecdsa-sha1": x509.ECDSAWithSHA1,
- "http://www.w3.org/2001/04/xmldsig-more#ecdsa-sha256": x509.ECDSAWithSHA256,
- "http://www.w3.org/2001/04/xmldsig-more#ecdsa-sha384": x509.ECDSAWithSHA384,
- "http://www.w3.org/2001/04/xmldsig-more#ecdsa-sha512": x509.ECDSAWithSHA512,
- }
- CanonicalizationAlgorithms = map[string]CanonicalizationAlgorithm{
- "http://www.w3.org/2000/09/xmldsig#enveloped-signature": EnvelopedSignature{},
- "http://www.w3.org/2001/10/xml-exc-c14n#": ExclusiveCanonicalization{},
- "http://www.w3.org/2001/10/xml-exc-c14n#WithComments": ExclusiveCanonicalization{WithComments: true},
- }
- }
- // CanonicalizationAlgorithm defines an interface for processing an XML
- // document into a standard format.
- //
- // If any child elements are in the Transform node, the entire transform node
- // will be passed to the Process method through the transformXML parameter as an
- // XML string. This is necessary for transforms that need additional processing
- // data, like XPath (http://www.w3.org/TR/xmldsig-core/#sec-XPath). If there are
- // no child elements in Transform (or CanonicalizationMethod), then an empty
- // string will be passed through.
- type CanonicalizationAlgorithm interface {
- Process(inputXML string, transformXML string) (outputXML string, err error)
- }
- // CanonicalizationAlgorithms maps the CanonicalizationMethod or
- // Transform Algorithm URIs to a type that implements the
- // CanonicalizationAlgorithm interface.
- //
- // Implementations are provided for the following transforms:
- // http://www.w3.org/2001/10/xml-exc-c14n# (ExclusiveCanonicalization)
- // http://www.w3.org/2001/10/xml-exc-c14n#WithComments (ExclusiveCanonicalizationWithComments)
- // http://www.w3.org/2000/09/xmldsig#enveloped-signature (EnvelopedSignature)
- //
- // Custom implementations can be added to the map
- var CanonicalizationAlgorithms map[string]CanonicalizationAlgorithm
- var hashAlgorithms map[string]crypto.Hash
- var signatureAlgorithms map[string]x509.SignatureAlgorithm
- // signatureData provides options for verifying a signed XML document
- type signatureData struct {
- xml *etree.Document
- signature *etree.Element
- signedInfo *etree.Element
- sigValue string
- sigAlgorithm x509.SignatureAlgorithm
- canonAlgorithm CanonicalizationAlgorithm
- refIDAttribute string
- }
- // SetSignature can be used to assign an external signature for the XML doc
- // that Validator will verify
- func (s *signatureData) SetSignature(sig string) error {
- doc := etree.NewDocument()
- err := doc.ReadFromString(sig)
- s.signature = doc.Root()
- return err
- }
- func (s *signatureData) parseEnvelopedSignature() error {
- sig := s.xml.FindElement(".//Signature")
- if sig != nil {
- s.signature = sig
- } else {
- return errors.New("signedxml: Unable to find a unique signature element " +
- "in the xml document. The signature must either be enveloped in the " +
- "xml doc or externally assigned to Validator.SetSignature")
- }
- return nil
- }
- func (s *signatureData) parseSignedInfo() error {
- s.signedInfo = nil
- s.signedInfo = s.signature.SelectElement("SignedInfo")
- if s.signedInfo == nil {
- return errors.New("signedxml: unable to find SignedInfo element")
- }
- // move the Signature level namespace down to SignedInfo so that the signature
- // value will match up
- if s.signedInfo.Space != "" {
- attr := s.signature.SelectAttr(s.signedInfo.Space)
- if attr != nil {
- s.signedInfo.Attr = []etree.Attr{*attr}
- }
- } else {
- attr := s.signature.SelectAttr("xmlns")
- if attr != nil {
- s.signedInfo.Attr = []etree.Attr{*attr}
- }
- }
- // Copy SignedInfo xmlns: into itself if it does not exist and is defined as a root attribute
- root := s.xml.Root()
- if root != nil {
- sigNS := root.SelectAttr("xmlns:" + s.signedInfo.Space)
- if sigNS != nil {
- if s.signedInfo.SelectAttr("xmlns:"+s.signedInfo.Space) == nil {
- s.signedInfo.CreateAttr("xmlns:"+s.signedInfo.Space, sigNS.Value)
- }
- }
- }
- return nil
- }
- func (s *signatureData) parseSigValue() error {
- s.sigValue = ""
- sigValueElement := s.signature.SelectElement("SignatureValue")
- if sigValueElement != nil {
- s.sigValue = sigValueElement.Text()
- return nil
- }
- return errors.New("signedxml: unable to find SignatureValue")
- }
- func (s *signatureData) parseSigAlgorithm() error {
- s.sigAlgorithm = x509.UnknownSignatureAlgorithm
- sigMethod := s.signedInfo.SelectElement("SignatureMethod")
- var sigAlgoURI string
- if sigMethod == nil {
- return errors.New("signedxml: Unable to find SignatureMethod element")
- }
- sigAlgoURI = sigMethod.SelectAttrValue("Algorithm", "")
- if sigAlgoURI == "" {
- return errors.New("signedxml: Unable to find Algorithm in " +
- "SignatureMethod element")
- }
- sigAlgo, ok := signatureAlgorithms[sigAlgoURI]
- if ok {
- s.sigAlgorithm = sigAlgo
- return nil
- }
- return errors.New("signedxml: Unsupported Algorithm " + sigAlgoURI + " in " +
- "SignatureMethod")
- }
- func (s *signatureData) parseCanonAlgorithm() error {
- s.canonAlgorithm = nil
- canonMethod := s.signedInfo.SelectElement("CanonicalizationMethod")
- var canonAlgoURI string
- if canonMethod == nil {
- return errors.New("signedxml: Unable to find CanonicalizationMethod element")
- }
- canonAlgoURI = canonMethod.SelectAttrValue("Algorithm", "")
- if canonAlgoURI == "" {
- return errors.New("signedxml: Unable to find Algorithm in " +
- "CanonicalizationMethod element")
- }
- canonAlgo, ok := CanonicalizationAlgorithms[canonAlgoURI]
- if ok {
- s.canonAlgorithm = canonAlgo
- return nil
- }
- return errors.New("signedxml: Unsupported Algorithm " + canonAlgoURI + " in " +
- "CanonicalizationMethod")
- }
- func (s *signatureData) getReferencedXML(reference *etree.Element, inputDoc *etree.Document) (outputDoc *etree.Document, err error) {
- uri := reference.SelectAttrValue("URI", "")
- uri = strings.Replace(uri, "#", "", 1)
- // populate doc with the referenced xml from the Reference URI
- if uri == "" {
- outputDoc = inputDoc
- } else {
- refIDAttribute := "ID"
- if s.refIDAttribute != "" {
- refIDAttribute = s.refIDAttribute
- }
- path := fmt.Sprintf(".//[@%s='%s']", refIDAttribute, uri)
- e := inputDoc.FindElement(path)
- if e != nil {
- outputDoc = etree.NewDocument()
- outputDoc.SetRoot(e.Copy())
- } else {
- // SAML v1.1 Assertions use AssertionID
- path := fmt.Sprintf(".//[@AssertionID='%s']", uri)
- e := inputDoc.FindElement(path)
- if e != nil {
- outputDoc = etree.NewDocument()
- outputDoc.SetRoot(e.Copy())
- }
- }
- }
- if outputDoc == nil {
- return nil, errors.New("signedxml: unable to find refereced xml")
- }
- return outputDoc, nil
- }
- func getCertFromPEMString(pemString string) (*x509.Certificate, error) {
- pubkey := fmt.Sprintf("-----BEGIN PUBLIC KEY-----\n%s\n-----END PUBLIC KEY-----",
- pemString)
- pemBlock, _ := pem.Decode([]byte(pubkey))
- if pemBlock == nil {
- return &x509.Certificate{}, errors.New("Could not parse Public Key PEM")
- }
- if pemBlock.Type != "PUBLIC KEY" {
- return &x509.Certificate{}, errors.New("Found wrong key type")
- }
- cert, err := x509.ParseCertificate(pemBlock.Bytes)
- return cert, err
- }
- func processTransform(transform *etree.Element,
- docIn *etree.Document) (docOut *etree.Document, err error) {
- transformAlgoURI := transform.SelectAttrValue("Algorithm", "")
- if transformAlgoURI == "" {
- return nil, errors.New("signedxml: unable to find Algorithm in Transform")
- }
- transformAlgo, ok := CanonicalizationAlgorithms[transformAlgoURI]
- if !ok {
- return nil, fmt.Errorf("signedxml: unable to find matching transform"+
- "algorithm for %s in CanonicalizationAlgorithms", transformAlgoURI)
- }
- var transformContent string
- if transform.ChildElements() != nil {
- tDoc := etree.NewDocument()
- tDoc.SetRoot(transform.Copy())
- transformContent, err = tDoc.WriteToString()
- if err != nil {
- return nil, err
- }
- }
- docString, err := docIn.WriteToString()
- if err != nil {
- return nil, err
- }
- docString, err = transformAlgo.Process(docString, transformContent)
- if err != nil {
- return nil, err
- }
- docOut = etree.NewDocument()
- docOut.ReadFromString(docString)
- return docOut, nil
- }
- func calculateHash(reference *etree.Element, doc *etree.Document) (string, error) {
- digestMethodElement := reference.SelectElement("DigestMethod")
- if digestMethodElement == nil {
- return "", errors.New("signedxml: unable to find DigestMethod")
- }
- digestMethodURI := digestMethodElement.SelectAttrValue("Algorithm", "")
- if digestMethodURI == "" {
- return "", errors.New("signedxml: unable to find Algorithm in DigestMethod")
- }
- digestAlgo, ok := hashAlgorithms[digestMethodURI]
- if !ok {
- return "", fmt.Errorf("signedxml: unable to find matching hash"+
- "algorithm for %s in hashAlgorithms", digestMethodURI)
- }
- doc.WriteSettings.CanonicalEndTags = true
- doc.WriteSettings.CanonicalText = true
- doc.WriteSettings.CanonicalAttrVal = true
- h := digestAlgo.New()
- docBytes, err := doc.WriteToBytes()
- if err != nil {
- return "", err
- }
- // ioutil.WriteFile("C:/Temp/SignedXML/Suspect.xml", docBytes, 0644)
- // s, _ := doc.WriteToString()
- // logger.Println(s)
- h.Write(docBytes)
- d := h.Sum(nil)
- calculatedValue := base64.StdEncoding.EncodeToString(d)
- return calculatedValue, nil
- }
|