172 lines
4.4 KiB
Go
172 lines
4.4 KiB
Go
package signedxml
|
|
|
|
import (
|
|
"crypto"
|
|
"crypto/rand"
|
|
"crypto/rsa"
|
|
"crypto/x509"
|
|
"encoding/base64"
|
|
"errors"
|
|
|
|
"github.com/beevik/etree"
|
|
)
|
|
|
|
var signingAlgorithms map[x509.SignatureAlgorithm]cryptoHash
|
|
|
|
func init() {
|
|
signingAlgorithms = map[x509.SignatureAlgorithm]cryptoHash{
|
|
// MD2 not supported
|
|
// x509.MD2WithRSA: cryptoHash{algorithm: "rsa", hash: crypto.MD2},
|
|
x509.MD5WithRSA: cryptoHash{algorithm: "rsa", hash: crypto.MD5},
|
|
x509.SHA1WithRSA: cryptoHash{algorithm: "rsa", hash: crypto.SHA1},
|
|
x509.SHA256WithRSA: cryptoHash{algorithm: "rsa", hash: crypto.SHA256},
|
|
x509.SHA384WithRSA: cryptoHash{algorithm: "rsa", hash: crypto.SHA384},
|
|
x509.SHA512WithRSA: cryptoHash{algorithm: "rsa", hash: crypto.SHA512},
|
|
// DSA not supported
|
|
// x509.DSAWithSHA1: cryptoHash{algorithm: "dsa", hash: crypto.SHA1},
|
|
// x509.DSAWithSHA256:cryptoHash{algorithm: "dsa", hash: crypto.SHA256},
|
|
// Golang ECDSA support is lacking, can't seem to load private keys
|
|
// x509.ECDSAWithSHA1: cryptoHash{algorithm: "ecdsa", hash: crypto.SHA1},
|
|
// x509.ECDSAWithSHA256: cryptoHash{algorithm: "ecdsa", hash: crypto.SHA256},
|
|
// x509.ECDSAWithSHA384: cryptoHash{algorithm: "ecdsa", hash: crypto.SHA384},
|
|
// x509.ECDSAWithSHA512: cryptoHash{algorithm: "ecdsa", hash: crypto.SHA512},
|
|
}
|
|
}
|
|
|
|
type cryptoHash struct {
|
|
algorithm string
|
|
hash crypto.Hash
|
|
}
|
|
|
|
// Signer provides options for signing an XML document
|
|
type Signer struct {
|
|
signatureData
|
|
privateKey interface{}
|
|
}
|
|
|
|
// NewSigner returns a *Signer for the XML provided
|
|
func NewSigner(xml string) (*Signer, error) {
|
|
doc := etree.NewDocument()
|
|
err := doc.ReadFromString(xml)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
s := &Signer{signatureData: signatureData{xml: doc}}
|
|
return s, nil
|
|
}
|
|
|
|
// Sign populates the XML digest and signature based on the parameters present and privateKey given
|
|
func (s *Signer) Sign(privateKey interface{}) (string, error) {
|
|
s.privateKey = privateKey
|
|
|
|
if s.signature == nil {
|
|
if err := s.parseEnvelopedSignature(); err != nil {
|
|
return "", err
|
|
}
|
|
}
|
|
if err := s.parseSignedInfo(); err != nil {
|
|
return "", err
|
|
}
|
|
if err := s.parseSigAlgorithm(); err != nil {
|
|
return "", err
|
|
}
|
|
if err := s.parseCanonAlgorithm(); err != nil {
|
|
return "", err
|
|
}
|
|
if err := s.setDigest(); err != nil {
|
|
return "", err
|
|
}
|
|
if err := s.setSignature(); err != nil {
|
|
return "", err
|
|
}
|
|
|
|
xml, err := s.xml.WriteToString()
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
return xml, nil
|
|
}
|
|
|
|
func (s *Signer) setDigest() (err error) {
|
|
references := s.signedInfo.FindElements("./Reference")
|
|
for _, ref := range references {
|
|
doc := s.xml.Copy()
|
|
transforms := ref.SelectElement("Transforms")
|
|
for _, transform := range transforms.SelectElements("Transform") {
|
|
doc, err = processTransform(transform, doc)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
}
|
|
|
|
doc, err := getReferencedXML(ref, doc)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
calculatedValue, err := calculateHash(ref, doc)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
digestValueElement := ref.SelectElement("DigestValue")
|
|
if digestValueElement == nil {
|
|
return errors.New("signedxml: unable to find DigestValue")
|
|
}
|
|
digestValueElement.SetText(calculatedValue)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (s *Signer) setSignature() error {
|
|
doc := etree.NewDocument()
|
|
doc.SetRoot(s.signedInfo.Copy())
|
|
signedInfo, err := doc.WriteToString()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
canonSignedInfo, err := s.canonAlgorithm.Process(signedInfo, "")
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
var hashed, signature []byte
|
|
//var h1, h2 *big.Int
|
|
signingAlgorithm, ok := signingAlgorithms[s.sigAlgorithm]
|
|
if !ok {
|
|
return errors.New("signedxml: unsupported algorithm")
|
|
}
|
|
|
|
hasher := signingAlgorithm.hash.New()
|
|
hasher.Write([]byte(canonSignedInfo))
|
|
hashed = hasher.Sum(nil)
|
|
|
|
switch signingAlgorithm.algorithm {
|
|
case "rsa":
|
|
signature, err = rsa.SignPKCS1v15(rand.Reader, s.privateKey.(*rsa.PrivateKey), signingAlgorithm.hash, hashed)
|
|
/*
|
|
case "dsa":
|
|
h1, h2, err = dsa.Sign(rand.Reader, s.privateKey.(*dsa.PrivateKey), hashed)
|
|
case "ecdsa":
|
|
h1, h2, err = ecdsa.Sign(rand.Reader, s.privateKey.(*ecdsa.PrivateKey), hashed)
|
|
*/
|
|
}
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
// DSA and ECDSA has not been validated
|
|
/*
|
|
if signature == nil && h1 != nil && h2 != nil {
|
|
signature = append(h1.Bytes(), h2.Bytes()...)
|
|
}
|
|
*/
|
|
|
|
b64 := base64.StdEncoding.EncodeToString(signature)
|
|
sigValueElement := s.signature.SelectElement("SignatureValue")
|
|
sigValueElement.SetText(b64)
|
|
|
|
return nil
|
|
}
|