1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17 package signjar
18
19 import (
20 "archive/zip"
21 "bytes"
22 "crypto"
23 "encoding/base64"
24 "fmt"
25 "hash"
26 "io"
27 "io/ioutil"
28 "net/http"
29 "path"
30 "strings"
31
32 "github.com/pkg/errors"
33 "github.com/sassoftware/relic/lib/pkcs7"
34 "github.com/sassoftware/relic/lib/pkcs9"
35 "github.com/sassoftware/relic/lib/x509tools"
36 "github.com/sassoftware/relic/signers/sigerrors"
37 )
38
39 var errNoDigests = errors.New("no recognized digests found")
40
41 type JarSignature struct {
42 pkcs9.TimestampedSignature
43 SignatureHeader http.Header
44 Hash crypto.Hash
45 }
46
47 func Verify(inz *zip.Reader, skipDigests bool) ([]*JarSignature, error) {
48 var manifest []byte
49 sigfiles := make(map[string][]byte)
50 sigblobs := make(map[string][]byte)
51 for _, f := range inz.File {
52 dir, name := path.Split(strings.ToUpper(f.Name))
53 if dir != "META-INF/" || name == "" {
54 continue
55 }
56 i := strings.LastIndex(name, ".")
57 if i < 0 {
58 continue
59 }
60 base, ext := name[:i], name[i:]
61 r2, err := f.Open()
62 if err != nil {
63 return nil, err
64 }
65 contents, err := ioutil.ReadAll(r2)
66 if err != nil {
67 return nil, err
68 }
69 if err := r2.Close(); err != nil {
70 return nil, err
71 }
72 if name == "MANIFEST.MF" {
73 manifest = contents
74 } else if ext == ".SF" {
75 sigfiles[base] = contents
76 } else if ext == ".RSA" || ext == ".DSA" || ext == ".EC" || strings.HasPrefix(name, "SIG-") {
77 sigblobs[base] = contents
78 }
79 }
80 if manifest == nil {
81 return nil, errors.New("JAR contains no META-INF/MANIFEST.MF")
82 } else if len(sigfiles) == 0 {
83 return nil, sigerrors.NotSignedError{Type: "JAR"}
84 }
85 sigs := make([]*JarSignature, 0, len(sigfiles))
86 for base, sigfile := range sigfiles {
87 pkcs := sigblobs[base]
88 if pkcs == nil {
89 return nil, fmt.Errorf("JAR contains sigfile META-INF/%s.SF with no matching signature", base)
90 }
91 psd, err := pkcs7.Unmarshal(pkcs)
92 if err != nil {
93 return nil, err
94 }
95 sig, err := psd.Content.Verify(sigfile, false)
96 if err != nil {
97 return nil, err
98 }
99 ts, err := pkcs9.VerifyOptionalTimestamp(sig)
100 if err != nil {
101 return nil, err
102 }
103 hdr, err := verifySigFile(sigfile, manifest)
104 if err != nil {
105 return nil, err
106 }
107 hash, _ := x509tools.PkixDigestToHash(ts.SignerInfo.DigestAlgorithm)
108 sigs = append(sigs, &JarSignature{
109 TimestampedSignature: ts,
110 Hash: hash,
111 SignatureHeader: hdr,
112 })
113 }
114 if !skipDigests {
115 if err := verifyManifest(inz, manifest); err != nil {
116 return nil, err
117 }
118 }
119 return sigs, nil
120 }
121
122
123 func verifyManifest(inz *zip.Reader, manifest []byte) error {
124 parsed, err := ParseManifest(manifest)
125 if err != nil {
126 return err
127 }
128 zipfiles := make(map[string]*zip.File, len(inz.File))
129 for _, fh := range inz.File {
130 zipfiles[fh.Name] = fh
131 }
132 for filename, keys := range parsed.Files {
133 if keys.Get("Magic") != "" {
134 continue
135 }
136 fh := zipfiles[filename]
137 if fh == nil {
138 return fmt.Errorf("file %s is in manifest but not JAR", filename)
139 }
140 r, err := fh.Open()
141 if err != nil {
142 return err
143 }
144 if err := hashFile(keys, r, ""); err != nil {
145 return errors.Wrapf(err, "file \"%s\" in MANIFEST.MF", filename)
146 }
147 if err := r.Close(); err != nil {
148 return err
149 }
150 }
151 return nil
152 }
153
154 type digester struct {
155 key, value string
156 hash hash.Hash
157 }
158
159
160 func hashFile(keys http.Header, content io.Reader, suffix string) error {
161 digesters := make([]digester, 0)
162 suffix = "-Digest" + suffix
163 for key, value := range keys {
164 if !strings.HasSuffix(key, suffix) {
165 continue
166 }
167 hashName := strings.ToUpper(key[:len(key)-len(suffix)])
168 hash := x509tools.HashByName(hashName)
169 if !hash.Available() {
170 return fmt.Errorf("unknown digest key in manifest: %s", hashName)
171 }
172 digesters = append(digesters, digester{key, value[0], hash.New()})
173 }
174 if len(digesters) == 0 {
175 return errNoDigests
176 }
177 buf := make([]byte, 32*1024)
178 for {
179 n, err := content.Read(buf)
180 if n > 0 {
181 for _, digester := range digesters {
182 digester.hash.Write(buf[:n])
183 }
184 }
185 if err == io.EOF {
186 break
187 } else if err != nil {
188 return err
189 }
190 }
191 for _, digester := range digesters {
192 calculated := base64.StdEncoding.EncodeToString(digester.hash.Sum(nil))
193 if calculated != digester.value {
194 return fmt.Errorf("%s mismatch: manifest %s != calculated %s", digester.key, digester.value, calculated)
195 }
196 }
197 return nil
198 }
199
200 func verifySigFile(sigfile, manifest []byte) (http.Header, error) {
201 sfParsed, err := ParseManifest(sigfile)
202 if err != nil {
203 return nil, err
204 }
205 if err := hashFile(sfParsed.Main, bytes.NewReader(manifest), "-Manifest"); err != nil {
206 if err != errNoDigests {
207 return nil, errors.Wrap(err, "manifest signature")
208 }
209
210 } else {
211
212 return sfParsed.Main, nil
213 }
214 sections, err := splitManifest(manifest)
215 if err != nil {
216 return nil, err
217 }
218 sectionMap := make(map[string][]byte, len(sections)-1)
219 for i, section := range sections {
220 if i == 0 {
221 if err := hashFile(sfParsed.Main, bytes.NewReader(sections[0]), "-Manifest-Main-Attributes"); err != nil {
222 return nil, errors.Wrap(err, "manifest main attributes signature")
223 }
224 } else {
225 hdr, err := parseSection(section)
226 if err != nil {
227 return nil, err
228 }
229 name := hdr.Get("Name")
230 if name == "" {
231 return nil, errors.New("manifest has section with no \"Name\" attribute")
232 }
233 sectionMap[name] = section
234 }
235 }
236 for name, keys := range sfParsed.Files {
237 section := sectionMap[name]
238 if section == nil {
239 return nil, fmt.Errorf("manifest is missing signed section \"%s\"", name)
240 }
241 if err := hashFile(keys, bytes.NewReader(section), ""); err != nil {
242 return nil, errors.Wrapf(err, "manifest signature over section \"%s\"", name)
243 }
244 }
245 return sfParsed.Main, nil
246 }
247
View as plain text