1
2
3
4
5
6
7
8
9
10
11
12
13
14
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
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
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
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
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
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
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
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
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