| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205 |
- package signedxml
- import (
- "crypto/x509"
- "encoding/base64"
- "errors"
- "fmt"
- "log"
- "github.com/beevik/etree"
- )
- // Validator provides options for verifying a signed XML document
- type Validator struct {
- Certificates []x509.Certificate
- signingCert x509.Certificate
- signatureData
- }
- // NewValidator returns a *Validator for the XML provided
- func NewValidator(xml string) (*Validator, error) {
- doc := etree.NewDocument()
- err := doc.ReadFromString(xml)
- if err != nil {
- return nil, err
- }
- v := &Validator{signatureData: signatureData{xml: doc}}
- return v, nil
- }
- // SetReferenceIDAttribute set the referenceIDAttribute
- func (v *Validator) SetReferenceIDAttribute(refIDAttribute string) {
- v.signatureData.refIDAttribute = refIDAttribute
- }
- // SetXML is used to assign the XML document that the Validator will verify
- func (v *Validator) SetXML(xml string) error {
- doc := etree.NewDocument()
- err := doc.ReadFromString(xml)
- v.xml = doc
- return err
- }
- // SigningCert returns the certificate, if any, that was used to successfully
- // validate the signature of the XML document. This will be a zero value
- // x509.Certificate before Validator.Validate is successfully called.
- func (v *Validator) SigningCert() x509.Certificate {
- return v.signingCert
- }
- // Validate validates the Reference digest values, and the signature value
- // over the SignedInfo.
- //
- // Deprecated: Use ValidateReferences instead
- func (v *Validator) Validate() error {
- _, err := v.ValidateReferences()
- return err
- }
- // ValidateReferences validates the Reference digest values, and the signature value
- // over the SignedInfo.
- //
- // If the signature is enveloped in the XML, then it will be used.
- // Otherwise, an external signature should be assigned using
- // Validator.SetSignature.
- //
- // The references returned by this method can be used to verify what was signed.
- func (v *Validator) ValidateReferences() ([]string, error) {
- if err := v.loadValuesFromXML(); err != nil {
- return nil, err
- }
- referenced, err := v.validateReferences()
- if err != nil {
- return nil, err
- }
- var ref []string
- for _, doc := range referenced {
- docStr, err := doc.WriteToString()
- if err != nil {
- return nil, err
- }
- ref = append(ref, docStr)
- }
- err = v.validateSignature()
- return ref, err
- }
- func (v *Validator) loadValuesFromXML() error {
- if v.signature == nil {
- if err := v.parseEnvelopedSignature(); err != nil {
- return err
- }
- }
- if err := v.parseSignedInfo(); err != nil {
- return err
- }
- if err := v.parseSigValue(); err != nil {
- return err
- }
- if err := v.parseSigAlgorithm(); err != nil {
- return err
- }
- if err := v.parseCanonAlgorithm(); err != nil {
- return err
- }
- if err := v.loadCertificates(); err != nil {
- return err
- }
- return nil
- }
- func (v *Validator) validateReferences() (referenced []*etree.Document, err error) {
- references := v.signedInfo.FindElements("./Reference")
- for _, ref := range references {
- doc := v.xml.Copy()
- transforms := ref.SelectElement("Transforms")
- for _, transform := range transforms.SelectElements("Transform") {
- doc, err = processTransform(transform, doc)
- if err != nil {
- return nil, err
- }
- }
- doc, err = v.getReferencedXML(ref, doc)
- if err != nil {
- return nil, err
- }
- referenced = append(referenced, doc)
- digestValueElement := ref.SelectElement("DigestValue")
- if digestValueElement == nil {
- return nil, errors.New("signedxml: unable to find DigestValue")
- }
- digestValue := digestValueElement.Text()
- calculatedValue, err := calculateHash(ref, doc)
- if err != nil {
- return nil, err
- }
- if calculatedValue != digestValue {
- return nil, fmt.Errorf("signedxml: Calculated digest does not match the"+
- " expected digestvalue of %s", digestValue)
- }
- }
- return referenced, nil
- }
- func (v *Validator) validateSignature() error {
- doc := etree.NewDocument()
- doc.SetRoot(v.signedInfo.Copy())
- signedInfo, err := doc.WriteToString()
- if err != nil {
- return err
- }
- canonSignedInfo, err := v.canonAlgorithm.Process(signedInfo, "")
- if err != nil {
- return err
- }
- b64, err := base64.StdEncoding.DecodeString(v.sigValue)
- if err != nil {
- return err
- }
- sig := []byte(b64)
- v.signingCert = x509.Certificate{}
- for _, cert := range v.Certificates {
- err := cert.CheckSignature(v.sigAlgorithm, []byte(canonSignedInfo), sig)
- if err == nil {
- v.signingCert = cert
- return nil
- }
- }
- return errors.New("signedxml: Calculated signature does not match the " +
- "SignatureValue provided")
- }
- func (v *Validator) loadCertificates() error {
- // If v.Certificates is already populated, then the client has already set it
- // to the desired cert. Otherwise, let's pull the public keys from the XML
- if len(v.Certificates) < 1 {
- keydata := v.xml.FindElements(".//X509Certificate")
- for _, key := range keydata {
- cert, err := getCertFromPEMString(key.Text())
- if err != nil {
- log.Printf("signedxml: Unable to load certificate: (%s). "+
- "Looking for another cert.", err)
- } else {
- v.Certificates = append(v.Certificates, *cert)
- }
- }
- }
- if len(v.Certificates) < 1 {
- return errors.New("signedxml: a certificate is required, but was not found")
- }
- return nil
- }
|