// // 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 signappx import ( "archive/zip" "bytes" "crypto/x509" "encoding/xml" "errors" "fmt" "io" "strings" "github.com/beevik/etree" "github.com/sassoftware/relic/lib/x509tools" ) type bundleManifest struct { XMLName xml.Name `xml:"http://schemas.microsoft.com/appx/2013/bundle Bundle"` SchemaVersion string `xml:",attr"` Identity appxIdentity Packages []bundlePackage `xml:"Packages>Package"` Etree *etree.Document `xml:"-"` } type bundlePackage struct { Type string `xml:",attr"` Version string `xml:",attr"` Architecture string `xml:",attr"` FileName string `xml:",attr"` Offset int64 `xml:",attr"` Size uint64 `xml:",attr"` } func verifyBundle(r io.ReaderAt, files zipFiles, sig *AppxSignature, skipDigests bool) error { blob, err := readZipFile(files[bundleManifestFile]) if err != nil { return fmt.Errorf("bundle manifest: %s", err) } var bundle bundleManifest if err := xml.Unmarshal(blob, &bundle); err != nil { return fmt.Errorf("bundle manifest: %s", err) } packages := make(map[string]int) for i, pkg := range bundle.Packages { packages[pkg.FileName] = i } sig.Bundled = make(map[string]*AppxSignature) publisher := x509tools.FormatPkixName(sig.Signature.Certificate.RawSubject, x509tools.NameStyleMsOsco) if bundle.Identity.Publisher != publisher { return fmt.Errorf("bundle manifest: publisher identity mismatch:\nexpected: %s\nactual: %s", publisher, bundle.Identity.Publisher) } for _, zf := range files { if !strings.HasSuffix(zf.Name, ".appx") { continue } if zf.Method != zip.Store { return errors.New("bundle manifest: contains compressed appx") } dosname := strings.Replace(zf.Name, "/", "\\", -1) pkgIndex, ok := packages[dosname] if !ok { return fmt.Errorf("bundle manifest: missing file %s", zf.Name) } packages[dosname] = -1 // mark as seen pkg := bundle.Packages[pkgIndex] offset, err := zf.DataOffset() if err != nil { return fmt.Errorf("bundle manifest: %s", err) } if pkg.Offset != offset { return fmt.Errorf("bundle manifest: %s claimed offset of %d but actual offset is %d", zf.Name, pkg.Offset, offset) } else if pkg.Size != zf.UncompressedSize64 { return fmt.Errorf("bundle manifest: %s claimed size of %d but actual size is %d", zf.Name, pkg.Size, zf.UncompressedSize64) } nested := io.NewSectionReader(r, offset, int64(zf.UncompressedSize64)) nestedSig, err := Verify(nested, int64(zf.UncompressedSize64), skipDigests) if err != nil { return fmt.Errorf("bundled file %s: %s", zf.Name, err) } if !bytes.Equal(nestedSig.Signature.Certificate.Raw, sig.Signature.Certificate.Raw) { return fmt.Errorf("bundled file %s signed by different publisher", zf.Name) } sig.Bundled[zf.Name] = nestedSig } for name, unseen := range packages { if unseen >= 0 { return fmt.Errorf("bundle missing file: %s", name) } } return nil } func parseBundle(blob []byte) (*bundleManifest, error) { manifest := new(bundleManifest) if err := xml.Unmarshal(blob, manifest); err != nil { return nil, err } manifest.Etree = etree.NewDocument() if err := manifest.Etree.ReadFromBytes(blob); err != nil { return nil, err } return manifest, nil } func (m *bundleManifest) SetPublisher(cert *x509.Certificate) { subj := x509tools.FormatPkixName(cert.RawSubject, x509tools.NameStyleMsOsco) m.Identity.Publisher = subj el := m.Etree.FindElement("Bundle/Identity") if el != nil { el.CreateAttr("Publisher", subj) } } func (m *bundleManifest) Marshal() ([]byte, error) { return m.Etree.WriteToBytes() }