1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17 package signjar
18
19 import (
20 "bytes"
21 "crypto"
22 "encoding/base64"
23 "errors"
24 "fmt"
25 "io"
26 "net/http"
27 "strings"
28
29 "github.com/sassoftware/relic/config"
30 "github.com/sassoftware/relic/lib/x509tools"
31 )
32
33
34
35 const (
36 metaInf = "META-INF/"
37 manifestName = metaInf + "MANIFEST.MF"
38 )
39
40 type FilesMap struct {
41 Main http.Header
42 Order []string
43 Files map[string]http.Header
44 }
45
46 func ParseManifest(manifest []byte) (files *FilesMap, err error) {
47 sections, err := splitManifest(manifest)
48 if err != nil {
49 return nil, err
50 }
51 files = &FilesMap{
52 Order: make([]string, 0, len(sections)-1),
53 Files: make(map[string]http.Header, len(sections)-1),
54 }
55 for i, section := range sections {
56 if i > 0 && len(section) == 0 {
57 continue
58 }
59 hdr, err := parseSection(section)
60 if err != nil {
61 return nil, err
62 }
63 if i == 0 {
64 files.Main = hdr
65 } else {
66 name := hdr.Get("Name")
67 if name == "" {
68 return nil, errors.New("manifest has section with no \"Name\" attribute")
69 }
70 files.Order = append(files.Order, name)
71 files.Files[name] = hdr
72 }
73 }
74 return files, nil
75 }
76
77 func (m *FilesMap) Dump() []byte {
78 var out bytes.Buffer
79 writeSection(&out, m.Main, "Manifest-Version")
80 for _, name := range m.Order {
81 section := m.Files[name]
82 if section != nil {
83 writeSection(&out, section, "Name")
84 }
85 }
86 return out.Bytes()
87 }
88
89 func splitManifest(manifest []byte) ([][]byte, error) {
90 sections := make([][]byte, 0)
91 for len(manifest) != 0 {
92 i1 := bytes.Index(manifest, []byte("\r\n\r\n"))
93 i2 := bytes.Index(manifest, []byte("\n\n"))
94 var idx int
95 if i1 < 0 {
96 if i2 < 0 {
97 return nil, errors.New("trailing bytes after last newline")
98 }
99 idx = i2 + 2
100 } else {
101 idx = i1 + 4
102 }
103 section := manifest[:idx]
104 manifest = manifest[idx:]
105 sections = append(sections, section)
106 }
107 return sections, nil
108 }
109
110 func parseSection(section []byte) (http.Header, error) {
111 section = bytes.Replace(section, []byte("\r\n"), []byte{'\n'}, -1)
112 section = bytes.Replace(section, []byte("\n "), []byte{}, -1)
113 keys := bytes.Split(section, []byte{'\n'})
114 hdr := make(http.Header)
115 for _, line := range keys {
116 if len(line) == 0 {
117 continue
118 }
119 idx := bytes.IndexRune(line, ':')
120 if idx < 0 {
121 return nil, errors.New("jar manifest is malformed")
122 }
123 key := strings.TrimSpace(string(line[:idx]))
124 value := strings.TrimSpace(string(line[idx+1:]))
125 hdr.Set(key, value)
126 }
127 return hdr, nil
128 }
129
130 func hashSection(hash crypto.Hash, section []byte) string {
131 d := hash.New()
132 d.Write(section)
133 return base64.StdEncoding.EncodeToString(d.Sum(nil))
134 }
135
136
137
138 func DigestManifest(manifest []byte, hash crypto.Hash, sectionsOnly, apkV2 bool) ([]byte, error) {
139 sections, err := splitManifest(manifest)
140 if err != nil {
141 return nil, err
142 }
143 hashName := x509tools.HashNames[hash]
144 if hashName == "" {
145 return nil, errors.New("unsupported hash type")
146 }
147 var output bytes.Buffer
148 writeAttribute(&output, "Signature-Version", "1.0")
149 writeAttribute(&output, hashName+"-Digest-Manifest-Main-Attributes", hashSection(hash, sections[0]))
150 if !sectionsOnly {
151 writeAttribute(&output, hashName+"-Digest-Manifest", hashSection(hash, manifest))
152 }
153 writeAttribute(&output, "Created-By", fmt.Sprintf("%s (%s)", config.UserAgent, config.Author))
154 if apkV2 {
155 writeAttribute(&output, "X-Android-APK-Signed", "2")
156 }
157 output.WriteString("\r\n")
158 for _, section := range sections[1:] {
159 hdr, err := parseSection(section)
160 if err != nil {
161 return nil, err
162 }
163 name := hdr.Get("Name")
164 if name == "" {
165 return nil, errors.New("File section was missing Name attribute")
166 }
167 writeAttribute(&output, "Name", name)
168 writeAttribute(&output, hashName+"-Digest", hashSection(hash, section))
169 output.WriteString("\r\n")
170 }
171 return output.Bytes(), nil
172 }
173
174 const maxLineLength = 70
175
176
177 func writeAttribute(out io.Writer, key, value string) {
178 line := []byte(fmt.Sprintf("%s: %s", key, value))
179 for i := 0; i < len(line); i += maxLineLength {
180 j := i + maxLineLength
181 if j > len(line) {
182 j = len(line)
183 }
184 if i != 0 {
185 out.Write([]byte{' '})
186 }
187 out.Write(line[i:j])
188 out.Write([]byte("\r\n"))
189 }
190 }
191
192 func writeSection(out io.Writer, hdr http.Header, first string) {
193 value := hdr.Get(first)
194 if value != "" {
195 writeAttribute(out, first, value)
196 }
197 for key, values := range hdr {
198 if key == first {
199 continue
200 }
201 for _, value := range values {
202 writeAttribute(out, key, value)
203 }
204 }
205 out.Write([]byte("\r\n"))
206 }
207
View as plain text