...

Source file src/github.com/sassoftware/relic/lib/xmldsig/sign.go

Documentation: github.com/sassoftware/relic/lib/xmldsig

     1  //
     2  // Copyright (c) SAS Institute Inc.
     3  //
     4  // Licensed under the Apache License, Version 2.0 (the "License");
     5  // you may not use this file except in compliance with the License.
     6  // You may obtain a copy of the License at
     7  //
     8  //     http://www.apache.org/licenses/LICENSE-2.0
     9  //
    10  // Unless required by applicable law or agreed to in writing, software
    11  // distributed under the License is distributed on an "AS IS" BASIS,
    12  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    13  // See the License for the specific language governing permissions and
    14  // limitations under the License.
    15  //
    16  
    17  // Implements a useful subset of the xmldsig specification for creating
    18  // signatures over XML documents.
    19  package xmldsig
    20  
    21  import (
    22  	"crypto"
    23  	"crypto/ecdsa"
    24  	"crypto/rand"
    25  	"crypto/rsa"
    26  	"crypto/x509"
    27  	"encoding/base64"
    28  	"errors"
    29  	"fmt"
    30  	"math/big"
    31  
    32  	"github.com/sassoftware/relic/lib/x509tools"
    33  
    34  	"github.com/beevik/etree"
    35  )
    36  
    37  type SignOptions struct {
    38  	// Use non-standard namespace for SHA-256 found in Microsoft ClickOnce manifests
    39  	MsCompatHashNames bool
    40  	// Use REC namespace for c14n method instead of the finalized one
    41  	UseRecC14n bool
    42  	// Add the X509 certificate chain to the KeyInfo
    43  	IncludeX509 bool
    44  	// Add a KeyValue element with the public key
    45  	IncludeKeyValue bool
    46  }
    47  
    48  func (s SignOptions) c14nNamespace() string {
    49  	if s.UseRecC14n {
    50  		return AlgXMLExcC14nRec
    51  	} else {
    52  		return AlgXMLExcC14n
    53  	}
    54  }
    55  
    56  // Create an enveloped signature from the document rooted at "root", replacing
    57  // any existing signature and adding it as a last child of "parent".
    58  func Sign(root, parent *etree.Element, hash crypto.Hash, privKey crypto.Signer, certs []*x509.Certificate, opts SignOptions) error {
    59  	pubKey := privKey.Public()
    60  	if len(certs) < 1 || !x509tools.SameKey(pubKey, certs[0].PublicKey) {
    61  		return errors.New("xmldsig: first certificate must match private key")
    62  	}
    63  	RemoveElements(parent, "Signature")
    64  	// canonicalize the enveloping document and digest it
    65  	refDigest, err := hashCanon(root, hash)
    66  	hashAlg, sigAlg, err := hashAlgs(hash, pubKey, opts)
    67  	if err != nil {
    68  		return err
    69  	}
    70  	// build a signedinfo that references the enveloping document
    71  	signature := parent.CreateElement("Signature")
    72  	signature.CreateAttr("xmlns", NsXMLDsig)
    73  	signedinfo := buildSignedInfo(signature, "", hashAlg, sigAlg, refDigest, opts)
    74  	return finishSignature(signature, signedinfo, hash, privKey, certs, opts)
    75  }
    76  
    77  // Build an enveloping Signature document around the given Object element
    78  func SignEnveloping(object *etree.Element, hash crypto.Hash, privKey crypto.Signer, certs []*x509.Certificate, opts SignOptions) (*etree.Element, error) {
    79  	pubKey := privKey.Public()
    80  	if len(certs) < 1 || !x509tools.SameKey(pubKey, certs[0].PublicKey) {
    81  		return nil, errors.New("xmldsig: first certificate must match private key")
    82  	}
    83  	// insert the object into the signature element before canonicalizing so
    84  	// that the namespace gets pushed down properly
    85  	signature := etree.NewElement("Signature")
    86  	signature.CreateAttr("xmlns", NsXMLDsig)
    87  	signature.AddChild(object)
    88  	refDigest, err := hashCanon(object, hash)
    89  	hashAlg, sigAlg, err := hashAlgs(hash, pubKey, opts)
    90  	if err != nil {
    91  		return nil, err
    92  	}
    93  	if object.Tag != "Object" {
    94  		return nil, errors.New("object must have tag \"Object\"")
    95  	}
    96  	refId := object.SelectAttrValue("Id", "")
    97  	if refId == "" {
    98  		return nil, errors.New("object lacks an Id attribute")
    99  	}
   100  	// build a signedinfo that references the enveloping document
   101  	signedinfo := buildSignedInfo(signature, refId, hashAlg, sigAlg, refDigest, opts)
   102  	// canonicalize the signedinfo section and sign it
   103  	if err := finishSignature(signature, signedinfo, hash, privKey, certs, opts); err != nil {
   104  		return nil, err
   105  	}
   106  	signature.RemoveChild(object)
   107  	signature.AddChild(object)
   108  	return signature, nil
   109  }
   110  
   111  func buildSignedInfo(signature *etree.Element, refId, hashAlg, sigAlg string, refDigest []byte, opts SignOptions) *etree.Element {
   112  	signedinfo := signature.CreateElement("SignedInfo")
   113  	signedinfo.CreateElement("CanonicalizationMethod").CreateAttr("Algorithm", opts.c14nNamespace())
   114  	signedinfo.CreateElement("SignatureMethod").CreateAttr("Algorithm", sigAlg)
   115  	reference := signedinfo.CreateElement("Reference")
   116  	if refId == "" {
   117  		reference.CreateAttr("URI", "")
   118  	} else {
   119  		reference.CreateAttr("URI", "#"+refId)
   120  		reference.CreateAttr("Type", NsXMLDsig+"Object")
   121  	}
   122  	transforms := reference.CreateElement("Transforms")
   123  	if refId == "" {
   124  		transforms.CreateElement("Transform").CreateAttr("Algorithm", AlgDsigEnvelopedSignature)
   125  	}
   126  	transforms.CreateElement("Transform").CreateAttr("Algorithm", opts.c14nNamespace())
   127  	reference.CreateElement("DigestMethod").CreateAttr("Algorithm", hashAlg)
   128  	reference.CreateElement("DigestValue").SetText(base64.StdEncoding.EncodeToString(refDigest))
   129  	return signedinfo
   130  }
   131  
   132  func finishSignature(signature, signedinfo *etree.Element, hash crypto.Hash, privKey crypto.Signer, certs []*x509.Certificate, opts SignOptions) error {
   133  	siDigest, err := hashCanon(signedinfo, hash)
   134  	sig, err := privKey.Sign(rand.Reader, siDigest, hash)
   135  	if err != nil {
   136  		return err
   137  	}
   138  	// build the rest of the signature element
   139  	if _, ok := privKey.Public().(*ecdsa.PublicKey); ok {
   140  		// reformat the signature without ASN.1 structure
   141  		esig, err := x509tools.UnmarshalEcdsaSignature(sig)
   142  		if err != nil {
   143  			return err
   144  		}
   145  		sig = esig.Pack()
   146  	}
   147  	signature.CreateElement("SignatureValue").SetText(base64.StdEncoding.EncodeToString(sig))
   148  	keyinfo := etree.NewElement("KeyInfo")
   149  	if opts.IncludeKeyValue {
   150  		if err := addKeyInfo(keyinfo, privKey.Public()); err != nil {
   151  			return err
   152  		}
   153  	}
   154  	if opts.IncludeX509 && len(certs) > 0 {
   155  		addCerts(keyinfo, certs)
   156  	}
   157  	if len(keyinfo.Child) > 0 {
   158  		signature.AddChild(keyinfo)
   159  	}
   160  	return nil
   161  }
   162  
   163  func hashCanon(root *etree.Element, hash crypto.Hash) ([]byte, error) {
   164  	canon, err := SerializeCanonical(root)
   165  	if err != nil {
   166  		return nil, fmt.Errorf("xmldsig: %s", err)
   167  	}
   168  	d := hash.New()
   169  	d.Write(canon)
   170  	return d.Sum(nil), nil
   171  }
   172  
   173  // Remove all child elements with this tag from the element
   174  func RemoveElements(root *etree.Element, tag string) {
   175  	for i := 0; i < len(root.Child); {
   176  		token := root.Child[i]
   177  		if elem, ok := token.(*etree.Element); ok && elem.Tag == tag {
   178  			root.Child = append(root.Child[:i], root.Child[i+1:]...)
   179  		} else {
   180  			i++
   181  		}
   182  	}
   183  }
   184  
   185  // Determine algorithm URIs for hashing and signing
   186  func hashAlgs(hash crypto.Hash, pubKey crypto.PublicKey, opts SignOptions) (string, string, error) {
   187  	hashName := hashNames[hash]
   188  	if hashName == "" {
   189  		return "", "", errors.New("unsupported hash type")
   190  	}
   191  	var pubName string
   192  	switch pubKey.(type) {
   193  	case *rsa.PublicKey:
   194  		pubName = "rsa"
   195  	case *ecdsa.PublicKey:
   196  		pubName = "ecdsa"
   197  	default:
   198  		return "", "", errors.New("unsupported key type")
   199  	}
   200  	var hashAlg, sigAlg string
   201  	if opts.MsCompatHashNames {
   202  		hashAlg = NsXMLDsig + hashName
   203  	} else {
   204  		hashAlg = HashUris[hash]
   205  	}
   206  	if pubName == "rsa" && (hashName == "sha1" || opts.MsCompatHashNames) {
   207  		sigAlg = NsXMLDsig + pubName + "-" + hashName
   208  	} else {
   209  		sigAlg = NsXMLDsigMore + pubName + "-" + hashName
   210  	}
   211  	return hashAlg, sigAlg, nil
   212  }
   213  
   214  // Add public key and optional X509 certificate chain to KeyInfo
   215  func addKeyInfo(keyinfo *etree.Element, pubKey crypto.PublicKey) error {
   216  	keyvalue := keyinfo.CreateElement("KeyValue")
   217  	switch k := pubKey.(type) {
   218  	case *rsa.PublicKey:
   219  		e := big.NewInt(int64(k.E))
   220  		rkv := keyvalue.CreateElement("RSAKeyValue")
   221  		rkv.CreateElement("Modulus").SetText(base64.StdEncoding.EncodeToString(k.N.Bytes()))
   222  		rkv.CreateElement("Exponent").SetText(base64.StdEncoding.EncodeToString(e.Bytes()))
   223  	case *ecdsa.PublicKey:
   224  		curve, err := x509tools.CurveByCurve(k.Curve)
   225  		if err != nil {
   226  			return err
   227  		}
   228  		curveUrn := fmt.Sprintf("urn:oid:%s", curve.Oid)
   229  		ekv := keyvalue.CreateElement("ECDSAKeyValue")
   230  		ekv.CreateElement("DomainParameters").CreateElement("NamedCurve").CreateAttr("URN", curveUrn)
   231  		pk := ekv.CreateElement("PublicKey")
   232  		x := pk.CreateElement("X")
   233  		x.CreateAttr("Value", k.X.String())
   234  		x.CreateAttr("xmlns:xsi", NsXsi)
   235  		x.CreateAttr("xsi:type", "PrimeFieldElemType")
   236  		y := pk.CreateElement("Y")
   237  		y.CreateAttr("Value", k.Y.String())
   238  		y.CreateAttr("xmlns:xsi", NsXsi)
   239  		y.CreateAttr("xsi:type", "PrimeFieldElemType")
   240  	default:
   241  		return errors.New("unsupported key type")
   242  	}
   243  	return nil
   244  }
   245  
   246  func addCerts(keyinfo *etree.Element, certs []*x509.Certificate) {
   247  	x509data := keyinfo.CreateElement("X509Data")
   248  	for _, cert := range certs {
   249  		x509data.CreateElement("X509Certificate").SetText(base64.StdEncoding.EncodeToString(cert.Raw))
   250  	}
   251  }
   252  

View as plain text