1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17 package signappx
18
19 import (
20 "archive/zip"
21 "bytes"
22 "crypto/hmac"
23 "errors"
24 "fmt"
25 "io"
26 "io/ioutil"
27
28 "github.com/sassoftware/relic/lib/authenticode"
29 "github.com/sassoftware/relic/lib/pkcs7"
30 "github.com/sassoftware/relic/lib/pkcs9"
31 "github.com/sassoftware/relic/lib/x509tools"
32 "github.com/sassoftware/relic/signers/sigerrors"
33 )
34
35 func Verify(r io.ReaderAt, size int64, skipDigests bool) (*AppxSignature, error) {
36 inz, err := zip.NewReader(r, size)
37 if err != nil {
38 return nil, err
39 }
40 files := make(zipFiles, len(inz.File))
41 for _, file := range inz.File {
42 files[file.Name] = file
43 }
44 sig, err := readSignature(files[appxSignature])
45 if err != nil {
46 return nil, err
47 }
48 sig.IsBundle = files[bundleManifestFile] != nil
49 if err := verifyFile(files, sig, "AXBM", appxBlockMap); err != nil {
50 return nil, err
51 }
52 if err := verifyFile(files, sig, "AXCI", appxCodeIntegrity); err != nil {
53 return nil, err
54 }
55 if err := verifyFile(files, sig, "AXCT", appxContentTypes); err != nil {
56 return nil, err
57 }
58 if err := verifyBlockMap(inz, files, skipDigests); err != nil {
59 return nil, err
60 }
61 if err := verifyCatalog(files[appxCodeIntegrity], sig); err != nil {
62 return nil, err
63 }
64 if err := verifyMeta(r, size, sig, skipDigests); err != nil {
65 return nil, err
66 }
67 if sig.IsBundle {
68 if err := verifyBundle(r, files, sig, skipDigests); err != nil {
69 return nil, err
70 }
71 } else {
72 if err := checkManifest(files, sig); err != nil {
73 return nil, err
74 }
75 }
76 return sig, nil
77 }
78
79 func readSignature(zf *zip.File) (*AppxSignature, error) {
80 if zf == nil {
81 return nil, sigerrors.NotSignedError{Type: "appx"}
82 }
83 blob, err := readZipFile(zf)
84 if err != nil {
85 return nil, err
86 }
87 if !bytes.HasPrefix(blob, []byte("PKCX")) {
88 return nil, errors.New("invalid appx signature")
89 }
90 psd, err := pkcs7.Unmarshal(blob[4:])
91 if err != nil {
92 return nil, fmt.Errorf("invalid appx signature: %s", err)
93 }
94 if !psd.Content.ContentInfo.ContentType.Equal(authenticode.OidSpcIndirectDataContent) {
95 return nil, fmt.Errorf("invalid appx signature: %s", "not an authenticode signature")
96 }
97 pksig, err := psd.Content.Verify(nil, false)
98 if err != nil {
99 return nil, fmt.Errorf("invalid appx signature: %s", err)
100 }
101 ts, err := pkcs9.VerifyOptionalTimestamp(pksig)
102 if err != nil {
103 return nil, err
104 }
105 indirect := new(authenticode.SpcIndirectDataContentMsi)
106 if err := psd.Content.ContentInfo.Unmarshal(indirect); err != nil {
107 return nil, fmt.Errorf("invalid appx signature: %s", err)
108 }
109 hash, err := x509tools.PkixDigestToHashE(indirect.MessageDigest.DigestAlgorithm)
110 if err != nil {
111 return nil, err
112 }
113 digests := indirect.MessageDigest.Digest
114 if !bytes.HasPrefix(digests, []byte("APPX")) {
115 return nil, errors.New("invalid appx signature")
116 }
117 digests = digests[4:]
118 digestmap := make(map[string][]byte)
119 for len(digests) > 0 {
120 if len(digests) < 4+hash.Size() {
121 return nil, errors.New("invalid appx signature")
122 }
123 name := string(digests[:4])
124 digestmap[name] = digests[4 : 4+hash.Size()]
125 digests = digests[4+hash.Size():]
126 }
127 return &AppxSignature{
128 Signature: &ts,
129 Hash: hash,
130 HashValues: digestmap,
131 }, nil
132 }
133
134 func verifyFile(files zipFiles, sig *AppxSignature, tag, name string) error {
135 expected := sig.HashValues[tag]
136 zf := files[name]
137 if zf == nil {
138 if expected == nil {
139 return nil
140 }
141 return fmt.Errorf("appx missing signed file: %s", name)
142 } else if expected == nil {
143 return fmt.Errorf("appx missing signature for file: %s", name)
144 }
145 r, err := zf.Open()
146 if err != nil {
147 return err
148 }
149 d := sig.Hash.New()
150 if _, err := io.Copy(d, r); err != nil {
151 return err
152 }
153 if err := r.Close(); err != nil {
154 return err
155 }
156 calc := d.Sum(nil)
157 if !hmac.Equal(calc, expected) {
158 return fmt.Errorf("appx digest mismatch for %s: calculated %x != found %x", name, calc, expected)
159 }
160 return nil
161 }
162
163 func readZipFile(zf *zip.File) ([]byte, error) {
164 if zf == nil {
165 return nil, errors.New("file not found")
166 }
167 r, err := zf.Open()
168 if err != nil {
169 return nil, err
170 }
171 blob, err := ioutil.ReadAll(r)
172 if err != nil {
173 return nil, err
174 }
175 if err := r.Close(); err != nil {
176 return nil, err
177 }
178 return blob, nil
179 }
180
181 func verifyCatalog(zf *zip.File, sig *AppxSignature) error {
182 if zf == nil {
183 if sig.IsBundle {
184 return nil
185 }
186 return errors.New("missing security catalog")
187 }
188 blob, err := readZipFile(zf)
189 if err != nil {
190 return err
191 }
192 psd, err := pkcs7.Unmarshal(blob)
193 if err != nil {
194 return fmt.Errorf("security catalog: %s", err)
195 }
196 if !psd.Content.ContentInfo.ContentType.Equal(authenticode.OidCertTrustList) {
197 return fmt.Errorf("security catalog: %s", "not a security catalog")
198 }
199 pksig, err := psd.Content.Verify(nil, false)
200 if err != nil {
201 return fmt.Errorf("security catalog: %s", err)
202 }
203 ts, err := pkcs9.VerifyOptionalTimestamp(pksig)
204 if err != nil {
205 return fmt.Errorf("security catalog: %s", err)
206 }
207 if !bytes.Equal(ts.Certificate.Raw, sig.Signature.Certificate.Raw) {
208 return fmt.Errorf("security catalog: %s", "catalog signed by different certificate than appx")
209 }
210
211 return nil
212 }
213
View as plain text