...

Source file src/github.com/sassoftware/relic/signers/vsix/oxmlsig.go

Documentation: github.com/sassoftware/relic/signers/vsix

     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  package vsix
    18  
    19  import (
    20  	"crypto"
    21  	"crypto/hmac"
    22  	"crypto/x509"
    23  	"encoding/base32"
    24  	"encoding/base64"
    25  	"encoding/xml"
    26  	"errors"
    27  	"fmt"
    28  	"io"
    29  	"path"
    30  	"sort"
    31  	"strings"
    32  
    33  	"github.com/beevik/etree"
    34  	"github.com/sassoftware/relic/lib/certloader"
    35  	"github.com/sassoftware/relic/lib/pkcs7"
    36  	"github.com/sassoftware/relic/lib/pkcs9"
    37  	"github.com/sassoftware/relic/lib/xmldsig"
    38  	"github.com/sassoftware/relic/signers"
    39  	"github.com/sassoftware/relic/signers/sigerrors"
    40  )
    41  
    42  type oxmlManifest struct {
    43  	References []reference `xml:"Manifest>Reference"`
    44  	Properties []property  `xml:"SignatureProperties>SignatureProperty"`
    45  }
    46  
    47  type reference struct {
    48  	URI          string   `xml:",attr"`
    49  	Transforms   []method `xml:"Transforms>Transform"`
    50  	DigestMethod method
    51  	DigestValue  string
    52  }
    53  
    54  type method struct {
    55  	Algorithm string `xml:",attr"`
    56  }
    57  
    58  type property struct {
    59  	Id                  string `xml:",attr"`
    60  	SignatureTimeFormat string `xml:"SignatureTime>Format"`
    61  	SignatureTimeValue  string `xml:"SignatureTime>Value"`
    62  }
    63  
    64  func checkManifest(files zipFiles, manifest *etree.Element) error {
    65  	doc := etree.NewDocument()
    66  	doc.SetRoot(manifest.Copy())
    67  	blob, err := doc.WriteToBytes()
    68  	if err != nil {
    69  		return fmt.Errorf("validation failed: %s", err)
    70  	}
    71  	var m oxmlManifest
    72  	if err := xml.Unmarshal(blob, &m); err != nil {
    73  		return fmt.Errorf("validation failed: %s", err)
    74  	}
    75  	for _, ref := range m.References {
    76  		p := path.Join("./" + ref.URI)
    77  		i := strings.IndexByte(p, '?')
    78  		if i >= 0 {
    79  			p = p[:i]
    80  		}
    81  		zf := files[p]
    82  		if zf == nil {
    83  			return fmt.Errorf("validation failed: file not found: %s", p)
    84  		}
    85  		f, err := zf.Open()
    86  		if err != nil {
    87  			return fmt.Errorf("validation failed: %s", err)
    88  		}
    89  		_, hash := xmldsig.HashAlgorithm(ref.DigestMethod.Algorithm)
    90  		if !hash.Available() {
    91  			return errors.New("validation failed: unsupported digest algorithm")
    92  		}
    93  		d := hash.New()
    94  		if _, err := io.Copy(d, f); err != nil {
    95  			return err
    96  		}
    97  		refCalc := d.Sum(nil)
    98  		refv, err := base64.StdEncoding.DecodeString(ref.DigestValue)
    99  		if err != nil {
   100  			return errors.New("validation failed: invalid digest")
   101  		}
   102  		if !hmac.Equal(refv, refCalc) {
   103  			return fmt.Errorf("validation failed: digest mismatch for %s: calculated %x, found %x", p, refCalc, refv)
   104  		}
   105  	}
   106  	return nil
   107  }
   108  
   109  func checkTimestamp(root *etree.Element, encryptedDigest []byte) (*pkcs9.CounterSignature, error) {
   110  	tsEl := root.FindElement("Object/TimeStamp/EncodedTime")
   111  	if tsEl == nil {
   112  		return nil, nil
   113  	}
   114  	blob, err := base64.StdEncoding.DecodeString(tsEl.Text())
   115  	if err != nil {
   116  		return nil, fmt.Errorf("timestamp check failed: %s", err)
   117  	}
   118  	tst, err := pkcs7.Unmarshal(blob)
   119  	if err != nil {
   120  		return nil, fmt.Errorf("timestamp check failed: %s", err)
   121  	}
   122  	return pkcs9.Verify(tst, encryptedDigest, nil)
   123  }
   124  
   125  // looks sorta like the official vsixsigntool output, close enough
   126  func calcFileName(cert *x509.Certificate) string {
   127  	d := crypto.SHA1.New()
   128  	d.Write(cert.Raw)
   129  	sum := d.Sum(nil)
   130  	return strings.ToLower(base32.StdEncoding.EncodeToString(sum))[:25]
   131  }
   132  
   133  func readSignature(files zipFiles) ([]byte, []*x509.Certificate, error) {
   134  	top := relPath("")
   135  	if files[top] == nil {
   136  		return nil, nil, sigerrors.NotSignedError{Type: "vsix"}
   137  	}
   138  	// top rels file
   139  	r, err := parseRels(files, top)
   140  	if err != nil {
   141  		return nil, nil, err
   142  	}
   143  	origin := r.Find(sigOriginType)
   144  	if origin == "" {
   145  		return nil, nil, sigerrors.NotSignedError{Type: "vsix"}
   146  	}
   147  	// signature rels file
   148  	r, err = parseRels(files, relPath(origin))
   149  	if err != nil {
   150  		return nil, nil, err
   151  	}
   152  	sigpath := r.Find(sigType)
   153  	if sigpath == "" {
   154  		return nil, nil, sigerrors.NotSignedError{Type: "vsix"}
   155  	}
   156  	sigblob, err := readZip(files, sigpath)
   157  	if err != nil {
   158  		return nil, nil, err
   159  	}
   160  	// certificates (optional)
   161  	var certs []*x509.Certificate
   162  	if files[relPath(sigpath)] != nil {
   163  		r, err := parseRels(files, relPath(sigpath))
   164  		if err != nil {
   165  			return nil, nil, err
   166  		}
   167  		for _, rel := range r.Relationship {
   168  			if rel.Type != certType {
   169  				continue
   170  			}
   171  			p := path.Clean("./" + rel.Target)
   172  			blob, err := readZip(files, p)
   173  			if err != nil {
   174  				return nil, nil, err
   175  			}
   176  			certs2, err := x509.ParseCertificates(blob)
   177  			if err != nil {
   178  				return nil, nil, fmt.Errorf("failed to parse certificate %s: %s", p, err)
   179  			}
   180  			certs = append(certs, certs2...)
   181  		}
   182  	}
   183  	return sigblob, certs, nil
   184  }
   185  
   186  func (m *mangler) makeSignature(cert *certloader.Certificate, opts signers.SignOpts, detachCerts bool) ([]byte, error) {
   187  	hashUri := xmldsig.HashUris[opts.Hash]
   188  	if hashUri == "" {
   189  		return nil, errors.New("unsupported digest algorithm")
   190  	}
   191  	pkg := etree.NewElement("Object")
   192  	pkg.CreateAttr("Id", "idPackageObject")
   193  	// file manifest
   194  	manifest := pkg.CreateElement("Manifest")
   195  	names := make([]string, 0, len(m.digests))
   196  	for name := range m.digests {
   197  		names = append(names, name)
   198  	}
   199  	sort.Strings(names)
   200  	for _, name := range names {
   201  		digest := m.digests[name]
   202  		ctype := m.ctypes.Find(name)
   203  		if ctype == "" {
   204  			ext := path.Ext(path.Base(name))
   205  			if ext[0] == '.' {
   206  				ctype = contentTypes[ext[1:]]
   207  			}
   208  		}
   209  		if ctype == "" {
   210  			ctype = defaultContentType
   211  		}
   212  		ref := manifest.CreateElement("Reference")
   213  		ref.CreateAttr("URI", "/"+name+"?ContentType="+ctype)
   214  		ref.CreateElement("DigestMethod").CreateAttr("Algorithm", hashUri)
   215  		ref.CreateElement("DigestValue").SetText(base64.StdEncoding.EncodeToString(digest))
   216  	}
   217  	// signature time
   218  	props := pkg.CreateElement("SignatureProperties")
   219  	proptime := props.CreateElement("SignatureProperty")
   220  	proptime.CreateAttr("Id", "idSignatureTime")
   221  	proptime.CreateAttr("Target", "")
   222  	sigtime := proptime.CreateElement("SignatureTime")
   223  	sigtime.CreateAttr("xmlns", nsDigSig)
   224  	sigtime.CreateElement("Format").SetText(tsFormatXML)
   225  	sigtime.CreateElement("Value").SetText(opts.Time.Format(tsFormatGo))
   226  	// sign
   227  	xopts := xmldsig.SignOptions{UseRecC14n: true, IncludeKeyValue: true}
   228  	if !detachCerts {
   229  		xopts.IncludeX509 = true
   230  	}
   231  	sigel, err := xmldsig.SignEnveloping(pkg, opts.Hash, cert.Signer(), cert.Chain(), xopts)
   232  	if err != nil {
   233  		return nil, err
   234  	}
   235  	// timestamp
   236  	if cert.Timestamper != nil {
   237  		encryptedDigest, _ := base64.StdEncoding.DecodeString(sigel.SelectElement("SignatureValue").Text())
   238  		req := &pkcs9.Request{EncryptedDigest: encryptedDigest, Hash: opts.Hash}
   239  		tst, err := cert.Timestamper.Timestamp(opts.Context(), req)
   240  		if err != nil {
   241  			return nil, fmt.Errorf("failed to timestamp signature: %s", err)
   242  		}
   243  		blob, err := tst.Marshal()
   244  		if err != nil {
   245  			return nil, fmt.Errorf("failed to timestamp signature: %s", err)
   246  		}
   247  		tsob := sigel.CreateElement("Object")
   248  		tsob.CreateAttr("xmlns", xmldsig.NsXMLDsig)
   249  		ts := tsob.CreateElement("TimeStamp")
   250  		ts.CreateAttr("xmlns", nsDigSig)
   251  		ts.CreateAttr("Id", "idSignatureTimestamp")
   252  		ts.CreateElement("Comment")
   253  		ts.CreateElement("EncodedTime").SetText(base64.StdEncoding.EncodeToString(blob))
   254  	}
   255  	doc := etree.NewDocument()
   256  	doc.SetRoot(sigel)
   257  	return doc.WriteToBytes()
   258  }
   259  

View as plain text