Files
go-xmlsec/vendor/github.com/ma314smith/signedxml/validator.go
2018-12-13 20:33:29 +01:00

201 lines
4.9 KiB
Go

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
}
// 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 = 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
}