// // Copyright (c) SAS Institute Inc. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. // package authenticode import ( "bytes" "crypto" "crypto/hmac" "encoding/binary" "errors" "fmt" "io" "io/ioutil" "sort" "github.com/sassoftware/relic/lib/comdoc" "github.com/sassoftware/relic/lib/pkcs7" "github.com/sassoftware/relic/lib/pkcs9" "github.com/sassoftware/relic/lib/x509tools" "github.com/sassoftware/relic/signers/sigerrors" ) type MSISignature struct { pkcs9.TimestampedSignature Indirect *SpcIndirectDataContentMsi HashFunc crypto.Hash } // Extract and verify the signature of a MSI file. Does not check X509 chains. func VerifyMSI(f io.ReaderAt, skipDigests bool) (*MSISignature, error) { cdf, err := comdoc.ReadFile(f) if err != nil { return nil, err } var sig, exsig []byte files, err := cdf.ListDir(nil) if err != nil { return nil, err } for _, item := range files { name := item.Name() if name == msiDigitalSignature { r, err := cdf.ReadStream(item) if err == nil { sig, err = ioutil.ReadAll(r) } if err != nil { return nil, err } } else if name == msiDigitalSignatureEx { r, err := cdf.ReadStream(item) if err == nil { exsig, err = ioutil.ReadAll(r) } if err != nil { return nil, err } } } if len(sig) == 0 { return nil, sigerrors.NotSignedError{Type: "MSI"} } psd, err := pkcs7.Unmarshal(sig) if err != nil { return nil, err } if !psd.Content.ContentInfo.ContentType.Equal(OidSpcIndirectDataContent) { return nil, errors.New("not an authenticode signature") } pksig, err := psd.Content.Verify(nil, false) if err != nil { return nil, err } ts, err := pkcs9.VerifyOptionalTimestamp(pksig) if err != nil { return nil, err } indirect := new(SpcIndirectDataContentMsi) if err := psd.Content.ContentInfo.Unmarshal(indirect); err != nil { return nil, err } hash, err := x509tools.PkixDigestToHashE(indirect.MessageDigest.DigestAlgorithm) if err != nil { return nil, err } msisig := &MSISignature{ TimestampedSignature: ts, Indirect: indirect, HashFunc: hash, } if !skipDigests { imprint, prehash, err := DigestMSI(cdf, hash, exsig != nil) if err != nil { return nil, err } if exsig != nil && !hmac.Equal(prehash, exsig) { return nil, fmt.Errorf("MSI extended digest mismatch: %x != %x", prehash, exsig) } if !hmac.Equal(imprint, indirect.MessageDigest.Digest) { return nil, fmt.Errorf("MSI digest mismatch: %x != %x", imprint, indirect.MessageDigest.Digest) } } return msisig, nil } // Calculate the digest (imprint) of a MSI file. If extended is true then the // MsiDigitalSignatureEx value is also hashed and returned. func DigestMSI(cdf *comdoc.ComDoc, hash crypto.Hash, extended bool) (imprint, prehash []byte, err error) { d := hash.New() if extended { prehash, err = PrehashMSI(cdf, hash) if err != nil { return nil, nil, err } d.Write(prehash) } if err := hashMsiDir(cdf, cdf.RootStorage(), d); err != nil { return nil, nil, err } imprint = d.Sum(nil) return } // Calculates the MsiDigitalSignatureEx blob for a MSI file func PrehashMSI(cdf *comdoc.ComDoc, hash crypto.Hash) ([]byte, error) { d2 := hash.New() if err := prehashMsiDir(cdf, cdf.RootStorage(), d2); err != nil { return nil, err } return d2.Sum(nil), nil } // Recursively hash a MSI directory (storage) func hashMsiDir(cdf *comdoc.ComDoc, parent *comdoc.DirEnt, d io.Writer) error { files, err := cdf.ListDir(parent) if err != nil { return err } sortMsiFiles(files) for _, item := range files { name := item.Name() if name == msiDigitalSignature || name == msiDigitalSignatureEx { continue } switch item.Type { case comdoc.DirStream: r, err := cdf.ReadStream(item) if err != nil { return err } if _, err := io.Copy(d, r); err != nil { return err } case comdoc.DirStorage: if err := hashMsiDir(cdf, item, d); err != nil { return err } } } d.Write(parent.UID[:]) return nil } // Recursively hash a MSI directory's extended metadata func prehashMsiDir(cdf *comdoc.ComDoc, parent *comdoc.DirEnt, d io.Writer) error { files, err := cdf.ListDir(parent) if err != nil { return err } sortMsiFiles(files) prehashMsiDirent(parent, d) for _, item := range files { name := item.Name() if name == msiDigitalSignature || name == msiDigitalSignatureEx { continue } switch item.Type { case comdoc.DirStream: prehashMsiDirent(item, d) case comdoc.DirStorage: if err := prehashMsiDir(cdf, item, d); err != nil { return err } } } return nil } // Hash a MSI stream's extended metadata func prehashMsiDirent(item *comdoc.DirEnt, d io.Writer) { buf := bytes.NewBuffer(make([]byte, 0, 128)) binary.Write(buf, binary.LittleEndian, item.RawDirEnt) enc := buf.Bytes() // Name if item.Type != comdoc.DirRoot { d.Write(enc[:item.NameLength-2]) } // UID if item.Type == comdoc.DirRoot || item.Type == comdoc.DirStorage { d.Write(item.UID[:]) } // Size if item.Type == comdoc.DirStream { d.Write(enc[120:124]) } // flags d.Write(enc[96:100]) // ctime, mtime if item.Type != comdoc.DirRoot { d.Write(enc[100:116]) } } // Sort a list of MSI streams in the order needed for hashing func sortMsiFiles(files []*comdoc.DirEnt) { sort.Slice(files, func(i, j int) bool { a, b := files[i], files[j] n := a.NameLength if b.NameLength < n { n = b.NameLength } // do a comparison of the utf16 in its original LE form for k := uint16(0); k < n; k++ { x, y := a.NameRunes[k], b.NameRunes[k] x1, y1 := x&0xff, y&0xff if x1 != y1 { return x1 < y1 } x2, y2 := x>>8, y>>8 if x2 != y2 { return x2 < y2 } } return a.NameLength > b.NameLength // yes, greater than }) }