...

Source file src/github.com/sassoftware/relic/lib/authenticode/msiverify.go

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

     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 authenticode
    18  
    19  import (
    20  	"bytes"
    21  	"crypto"
    22  	"crypto/hmac"
    23  	"encoding/binary"
    24  	"errors"
    25  	"fmt"
    26  	"io"
    27  	"io/ioutil"
    28  	"sort"
    29  
    30  	"github.com/sassoftware/relic/lib/comdoc"
    31  	"github.com/sassoftware/relic/lib/pkcs7"
    32  	"github.com/sassoftware/relic/lib/pkcs9"
    33  	"github.com/sassoftware/relic/lib/x509tools"
    34  	"github.com/sassoftware/relic/signers/sigerrors"
    35  )
    36  
    37  type MSISignature struct {
    38  	pkcs9.TimestampedSignature
    39  	Indirect *SpcIndirectDataContentMsi
    40  	HashFunc crypto.Hash
    41  }
    42  
    43  // Extract and verify the signature of a MSI file. Does not check X509 chains.
    44  func VerifyMSI(f io.ReaderAt, skipDigests bool) (*MSISignature, error) {
    45  	cdf, err := comdoc.ReadFile(f)
    46  	if err != nil {
    47  		return nil, err
    48  	}
    49  	var sig, exsig []byte
    50  	files, err := cdf.ListDir(nil)
    51  	if err != nil {
    52  		return nil, err
    53  	}
    54  	for _, item := range files {
    55  		name := item.Name()
    56  		if name == msiDigitalSignature {
    57  			r, err := cdf.ReadStream(item)
    58  			if err == nil {
    59  				sig, err = ioutil.ReadAll(r)
    60  			}
    61  			if err != nil {
    62  				return nil, err
    63  			}
    64  		} else if name == msiDigitalSignatureEx {
    65  			r, err := cdf.ReadStream(item)
    66  			if err == nil {
    67  				exsig, err = ioutil.ReadAll(r)
    68  			}
    69  			if err != nil {
    70  				return nil, err
    71  			}
    72  		}
    73  	}
    74  	if len(sig) == 0 {
    75  		return nil, sigerrors.NotSignedError{Type: "MSI"}
    76  	}
    77  	psd, err := pkcs7.Unmarshal(sig)
    78  	if err != nil {
    79  		return nil, err
    80  	}
    81  	if !psd.Content.ContentInfo.ContentType.Equal(OidSpcIndirectDataContent) {
    82  		return nil, errors.New("not an authenticode signature")
    83  	}
    84  	pksig, err := psd.Content.Verify(nil, false)
    85  	if err != nil {
    86  		return nil, err
    87  	}
    88  	ts, err := pkcs9.VerifyOptionalTimestamp(pksig)
    89  	if err != nil {
    90  		return nil, err
    91  	}
    92  	indirect := new(SpcIndirectDataContentMsi)
    93  	if err := psd.Content.ContentInfo.Unmarshal(indirect); err != nil {
    94  		return nil, err
    95  	}
    96  	hash, err := x509tools.PkixDigestToHashE(indirect.MessageDigest.DigestAlgorithm)
    97  	if err != nil {
    98  		return nil, err
    99  	}
   100  	msisig := &MSISignature{
   101  		TimestampedSignature: ts,
   102  		Indirect:             indirect,
   103  		HashFunc:             hash,
   104  	}
   105  	if !skipDigests {
   106  		imprint, prehash, err := DigestMSI(cdf, hash, exsig != nil)
   107  		if err != nil {
   108  			return nil, err
   109  		}
   110  		if exsig != nil && !hmac.Equal(prehash, exsig) {
   111  			return nil, fmt.Errorf("MSI extended digest mismatch: %x != %x", prehash, exsig)
   112  		}
   113  		if !hmac.Equal(imprint, indirect.MessageDigest.Digest) {
   114  			return nil, fmt.Errorf("MSI digest mismatch: %x != %x", imprint, indirect.MessageDigest.Digest)
   115  		}
   116  	}
   117  	return msisig, nil
   118  }
   119  
   120  // Calculate the digest (imprint) of a MSI file. If extended is true then the
   121  // MsiDigitalSignatureEx value is also hashed and returned.
   122  func DigestMSI(cdf *comdoc.ComDoc, hash crypto.Hash, extended bool) (imprint, prehash []byte, err error) {
   123  	d := hash.New()
   124  	if extended {
   125  		prehash, err = PrehashMSI(cdf, hash)
   126  		if err != nil {
   127  			return nil, nil, err
   128  		}
   129  		d.Write(prehash)
   130  	}
   131  	if err := hashMsiDir(cdf, cdf.RootStorage(), d); err != nil {
   132  		return nil, nil, err
   133  	}
   134  	imprint = d.Sum(nil)
   135  	return
   136  }
   137  
   138  // Calculates the MsiDigitalSignatureEx blob for a MSI file
   139  func PrehashMSI(cdf *comdoc.ComDoc, hash crypto.Hash) ([]byte, error) {
   140  	d2 := hash.New()
   141  	if err := prehashMsiDir(cdf, cdf.RootStorage(), d2); err != nil {
   142  		return nil, err
   143  	}
   144  	return d2.Sum(nil), nil
   145  }
   146  
   147  // Recursively hash a MSI directory (storage)
   148  func hashMsiDir(cdf *comdoc.ComDoc, parent *comdoc.DirEnt, d io.Writer) error {
   149  	files, err := cdf.ListDir(parent)
   150  	if err != nil {
   151  		return err
   152  	}
   153  	sortMsiFiles(files)
   154  	for _, item := range files {
   155  		name := item.Name()
   156  		if name == msiDigitalSignature || name == msiDigitalSignatureEx {
   157  			continue
   158  		}
   159  		switch item.Type {
   160  		case comdoc.DirStream:
   161  			r, err := cdf.ReadStream(item)
   162  			if err != nil {
   163  				return err
   164  			}
   165  			if _, err := io.Copy(d, r); err != nil {
   166  				return err
   167  			}
   168  		case comdoc.DirStorage:
   169  			if err := hashMsiDir(cdf, item, d); err != nil {
   170  				return err
   171  			}
   172  		}
   173  	}
   174  	d.Write(parent.UID[:])
   175  	return nil
   176  }
   177  
   178  // Recursively hash a MSI directory's extended metadata
   179  func prehashMsiDir(cdf *comdoc.ComDoc, parent *comdoc.DirEnt, d io.Writer) error {
   180  	files, err := cdf.ListDir(parent)
   181  	if err != nil {
   182  		return err
   183  	}
   184  	sortMsiFiles(files)
   185  	prehashMsiDirent(parent, d)
   186  	for _, item := range files {
   187  		name := item.Name()
   188  		if name == msiDigitalSignature || name == msiDigitalSignatureEx {
   189  			continue
   190  		}
   191  		switch item.Type {
   192  		case comdoc.DirStream:
   193  			prehashMsiDirent(item, d)
   194  		case comdoc.DirStorage:
   195  			if err := prehashMsiDir(cdf, item, d); err != nil {
   196  				return err
   197  			}
   198  		}
   199  	}
   200  	return nil
   201  }
   202  
   203  // Hash a MSI stream's extended metadata
   204  func prehashMsiDirent(item *comdoc.DirEnt, d io.Writer) {
   205  	buf := bytes.NewBuffer(make([]byte, 0, 128))
   206  	binary.Write(buf, binary.LittleEndian, item.RawDirEnt)
   207  	enc := buf.Bytes()
   208  	// Name
   209  	if item.Type != comdoc.DirRoot {
   210  		d.Write(enc[:item.NameLength-2])
   211  	}
   212  	// UID
   213  	if item.Type == comdoc.DirRoot || item.Type == comdoc.DirStorage {
   214  		d.Write(item.UID[:])
   215  	}
   216  	// Size
   217  	if item.Type == comdoc.DirStream {
   218  		d.Write(enc[120:124])
   219  	}
   220  	// flags
   221  	d.Write(enc[96:100])
   222  	// ctime, mtime
   223  	if item.Type != comdoc.DirRoot {
   224  		d.Write(enc[100:116])
   225  	}
   226  }
   227  
   228  // Sort a list of MSI streams in the order needed for hashing
   229  func sortMsiFiles(files []*comdoc.DirEnt) {
   230  	sort.Slice(files, func(i, j int) bool {
   231  		a, b := files[i], files[j]
   232  		n := a.NameLength
   233  		if b.NameLength < n {
   234  			n = b.NameLength
   235  		}
   236  		// do a comparison of the utf16 in its original LE form
   237  		for k := uint16(0); k < n; k++ {
   238  			x, y := a.NameRunes[k], b.NameRunes[k]
   239  			x1, y1 := x&0xff, y&0xff
   240  			if x1 != y1 {
   241  				return x1 < y1
   242  			}
   243  			x2, y2 := x>>8, y>>8
   244  			if x2 != y2 {
   245  				return x2 < y2
   246  			}
   247  		}
   248  		return a.NameLength > b.NameLength // yes, greater than
   249  	})
   250  }
   251  

View as plain text