getJcaSignatureAlgorithmAndParams() {
129 | return mJcaSignatureAlgAndParams;
130 | }
131 |
132 | static SignatureAlgorithm findById(int id) {
133 | for (SignatureAlgorithm alg : SignatureAlgorithm.values()) {
134 | if (alg.getId() == id) {
135 | return alg;
136 | }
137 | }
138 |
139 | return null;
140 | }
141 | }
142 |
--------------------------------------------------------------------------------
/apksig/src/com/android/apksig/internal/jar/ManifestParser.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (C) 2016 The Android Open Source Project
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | package com.android.apksig.internal.jar;
18 |
19 | import java.nio.charset.StandardCharsets;
20 | import java.util.ArrayList;
21 | import java.util.Arrays;
22 | import java.util.Collections;
23 | import java.util.List;
24 | import java.util.jar.Attributes;
25 |
26 | /**
27 | * JAR manifest and signature file parser.
28 | *
29 | * These files consist of a main section followed by individual sections. Individual sections
30 | * are named, their names referring to JAR entries.
31 | *
32 | * @see JAR Manifest format
33 | */
34 | public class ManifestParser {
35 |
36 | private final byte[] mManifest;
37 | private int mOffset;
38 | private int mEndOffset;
39 |
40 | private byte[] mBufferedLine;
41 |
42 | /**
43 | * Constructs a new {@code ManifestParser} with the provided input.
44 | */
45 | public ManifestParser(byte[] data) {
46 | this(data, 0, data.length);
47 | }
48 |
49 | /**
50 | * Constructs a new {@code ManifestParser} with the provided input.
51 | */
52 | public ManifestParser(byte[] data, int offset, int length) {
53 | mManifest = data;
54 | mOffset = offset;
55 | mEndOffset = offset + length;
56 | }
57 |
58 | /**
59 | * Returns the remaining sections of this file.
60 | */
61 | public List readAllSections() {
62 | List sections = new ArrayList<>();
63 | Section section;
64 | while ((section = readSection()) != null) {
65 | sections.add(section);
66 | }
67 | return sections;
68 | }
69 |
70 | /**
71 | * Returns the next section from this file or {@code null} if end of file has been reached.
72 | */
73 | public Section readSection() {
74 | // Locate the first non-empty line
75 | int sectionStartOffset;
76 | String attr;
77 | do {
78 | sectionStartOffset = mOffset;
79 | attr = readAttribute();
80 | if (attr == null) {
81 | return null;
82 | }
83 | } while (attr.length() == 0);
84 | List attrs = new ArrayList<>();
85 | attrs.add(parseAttr(attr));
86 |
87 | // Read attributes until end of section reached
88 | while (true) {
89 | attr = readAttribute();
90 | if ((attr == null) || (attr.length() == 0)) {
91 | // End of section
92 | break;
93 | }
94 | attrs.add(parseAttr(attr));
95 | }
96 |
97 | int sectionEndOffset = mOffset;
98 | int sectionSizeBytes = sectionEndOffset - sectionStartOffset;
99 |
100 | return new Section(sectionStartOffset, sectionSizeBytes, attrs);
101 | }
102 |
103 | private static Attribute parseAttr(String attr) {
104 | // Name is separated from value by a semicolon followed by a single SPACE character.
105 | // This permits trailing spaces in names and leading and trailing spaces in values.
106 | // Some APK obfuscators take advantage of this fact. We thus need to preserve these unusual
107 | // spaces to be able to parse such obfuscated APKs.
108 | int delimiterIndex = attr.indexOf(": ");
109 | if (delimiterIndex == -1) {
110 | return new Attribute(attr, "");
111 | } else {
112 | return new Attribute(
113 | attr.substring(0, delimiterIndex),
114 | attr.substring(delimiterIndex + ": ".length()));
115 | }
116 | }
117 |
118 | /**
119 | * Returns the next attribute or empty {@code String} if end of section has been reached or
120 | * {@code null} if end of input has been reached.
121 | */
122 | private String readAttribute() {
123 | byte[] bytes = readAttributeBytes();
124 | if (bytes == null) {
125 | return null;
126 | } else if (bytes.length == 0) {
127 | return "";
128 | } else {
129 | return new String(bytes, StandardCharsets.UTF_8);
130 | }
131 | }
132 |
133 | /**
134 | * Returns the next attribute or empty array if end of section has been reached or {@code null}
135 | * if end of input has been reached.
136 | */
137 | private byte[] readAttributeBytes() {
138 | // Check whether end of section was reached during previous invocation
139 | if ((mBufferedLine != null) && (mBufferedLine.length == 0)) {
140 | mBufferedLine = null;
141 | return EMPTY_BYTE_ARRAY;
142 | }
143 |
144 | // Read the next line
145 | byte[] line = readLine();
146 | if (line == null) {
147 | // End of input
148 | if (mBufferedLine != null) {
149 | byte[] result = mBufferedLine;
150 | mBufferedLine = null;
151 | return result;
152 | }
153 | return null;
154 | }
155 |
156 | // Consume the read line
157 | if (line.length == 0) {
158 | // End of section
159 | if (mBufferedLine != null) {
160 | byte[] result = mBufferedLine;
161 | mBufferedLine = EMPTY_BYTE_ARRAY;
162 | return result;
163 | }
164 | return EMPTY_BYTE_ARRAY;
165 | }
166 | byte[] attrLine;
167 | if (mBufferedLine == null) {
168 | attrLine = line;
169 | } else {
170 | if ((line.length == 0) || (line[0] != ' ')) {
171 | // The most common case: buffered line is a full attribute
172 | byte[] result = mBufferedLine;
173 | mBufferedLine = line;
174 | return result;
175 | }
176 | attrLine = mBufferedLine;
177 | mBufferedLine = null;
178 | attrLine = concat(attrLine, line, 1, line.length - 1);
179 | }
180 |
181 | // Everything's buffered in attrLine now. mBufferedLine is null
182 |
183 | // Read more lines
184 | while (true) {
185 | line = readLine();
186 | if (line == null) {
187 | // End of input
188 | return attrLine;
189 | } else if (line.length == 0) {
190 | // End of section
191 | mBufferedLine = EMPTY_BYTE_ARRAY; // return "end of section" next time
192 | return attrLine;
193 | }
194 | if (line[0] == ' ') {
195 | // Continuation line
196 | attrLine = concat(attrLine, line, 1, line.length - 1);
197 | } else {
198 | // Next attribute
199 | mBufferedLine = line;
200 | return attrLine;
201 | }
202 | }
203 | }
204 |
205 | private static final byte[] EMPTY_BYTE_ARRAY = new byte[0];
206 |
207 | private static byte[] concat(byte[] arr1, byte[] arr2, int offset2, int length2) {
208 | byte[] result = new byte[arr1.length + length2];
209 | System.arraycopy(arr1, 0, result, 0, arr1.length);
210 | System.arraycopy(arr2, offset2, result, arr1.length, length2);
211 | return result;
212 | }
213 |
214 | /**
215 | * Returns the next line (without line delimiter characters) or {@code null} if end of input has
216 | * been reached.
217 | */
218 | private byte[] readLine() {
219 | if (mOffset >= mEndOffset) {
220 | return null;
221 | }
222 | int startOffset = mOffset;
223 | int newlineStartOffset = -1;
224 | int newlineEndOffset = -1;
225 | for (int i = startOffset; i < mEndOffset; i++) {
226 | byte b = mManifest[i];
227 | if (b == '\r') {
228 | newlineStartOffset = i;
229 | int nextIndex = i + 1;
230 | if ((nextIndex < mEndOffset) && (mManifest[nextIndex] == '\n')) {
231 | newlineEndOffset = nextIndex + 1;
232 | break;
233 | }
234 | newlineEndOffset = nextIndex;
235 | break;
236 | } else if (b == '\n') {
237 | newlineStartOffset = i;
238 | newlineEndOffset = i + 1;
239 | break;
240 | }
241 | }
242 | if (newlineStartOffset == -1) {
243 | newlineStartOffset = mEndOffset;
244 | newlineEndOffset = mEndOffset;
245 | }
246 | mOffset = newlineEndOffset;
247 |
248 | if (newlineStartOffset == startOffset) {
249 | return EMPTY_BYTE_ARRAY;
250 | }
251 | return Arrays.copyOfRange(mManifest, startOffset, newlineStartOffset);
252 | }
253 |
254 |
255 | /**
256 | * Attribute.
257 | */
258 | public static class Attribute {
259 | private final String mName;
260 | private final String mValue;
261 |
262 | /**
263 | * Constructs a new {@code Attribute} with the provided name and value.
264 | */
265 | public Attribute(String name, String value) {
266 | mName = name;
267 | mValue = value;
268 | }
269 |
270 | /**
271 | * Returns this attribute's name.
272 | */
273 | public String getName() {
274 | return mName;
275 | }
276 |
277 | /**
278 | * Returns this attribute's value.
279 | */
280 | public String getValue() {
281 | return mValue;
282 | }
283 | }
284 |
285 | /**
286 | * Section.
287 | */
288 | public static class Section {
289 | private final int mStartOffset;
290 | private final int mSizeBytes;
291 | private final String mName;
292 | private final List mAttributes;
293 |
294 | /**
295 | * Constructs a new {@code Section}.
296 | *
297 | * @param startOffset start offset (in bytes) of the section in the input file
298 | * @param sizeBytes size (in bytes) of the section in the input file
299 | * @param attrs attributes contained in the section
300 | */
301 | public Section(int startOffset, int sizeBytes, List attrs) {
302 | mStartOffset = startOffset;
303 | mSizeBytes = sizeBytes;
304 | String sectionName = null;
305 | if (!attrs.isEmpty()) {
306 | Attribute firstAttr = attrs.get(0);
307 | if ("Name".equalsIgnoreCase(firstAttr.getName())) {
308 | sectionName = firstAttr.getValue();
309 | }
310 | }
311 | mName = sectionName;
312 | mAttributes = Collections.unmodifiableList(new ArrayList<>(attrs));
313 | }
314 |
315 | public String getName() {
316 | return mName;
317 | }
318 |
319 | /**
320 | * Returns the offset (in bytes) at which this section starts in the input.
321 | */
322 | public int getStartOffset() {
323 | return mStartOffset;
324 | }
325 |
326 | /**
327 | * Returns the size (in bytes) of this section in the input.
328 | */
329 | public int getSizeBytes() {
330 | return mSizeBytes;
331 | }
332 |
333 | /**
334 | * Returns this section's attributes, in the order in which they appear in the input.
335 | */
336 | public List getAttributes() {
337 | return mAttributes;
338 | }
339 |
340 | /**
341 | * Returns the value of the specified attribute in this section or {@code null} if this
342 | * section does not contain a matching attribute.
343 | */
344 | public String getAttributeValue(Attributes.Name name) {
345 | return getAttributeValue(name.toString());
346 | }
347 |
348 | /**
349 | * Returns the value of the specified attribute in this section or {@code null} if this
350 | * section does not contain a matching attribute.
351 | *
352 | * @param name name of the attribute. Attribute names are case-insensitive.
353 | */
354 | public String getAttributeValue(String name) {
355 | for (Attribute attr : mAttributes) {
356 | if (attr.getName().equalsIgnoreCase(name)) {
357 | return attr.getValue();
358 | }
359 | }
360 | return null;
361 | }
362 | }
363 | }
364 |
--------------------------------------------------------------------------------
/apksig/src/com/android/apksig/internal/jar/ManifestWriter.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (C) 2016 The Android Open Source Project
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | package com.android.apksig.internal.jar;
18 |
19 | import java.io.IOException;
20 | import java.io.OutputStream;
21 | import java.nio.charset.StandardCharsets;
22 | import java.util.Map;
23 | import java.util.Set;
24 | import java.util.SortedMap;
25 | import java.util.TreeMap;
26 | import java.util.jar.Attributes;
27 |
28 | /**
29 | * Producer of {@code META-INF/MANIFEST.MF} file.
30 | *
31 | * @see JAR Manifest format
32 | */
33 | public abstract class ManifestWriter {
34 |
35 | private static final byte[] CRLF = new byte[] {'\r', '\n'};
36 | private static final int MAX_LINE_LENGTH = 70;
37 |
38 | private ManifestWriter() {}
39 |
40 | public static void writeMainSection(OutputStream out, Attributes attributes)
41 | throws IOException {
42 |
43 | // Main section must start with the Manifest-Version attribute.
44 | // See https://docs.oracle.com/javase/8/docs/technotes/guides/jar/jar.html#Signed_JAR_File.
45 | String manifestVersion = attributes.getValue(Attributes.Name.MANIFEST_VERSION);
46 | if (manifestVersion == null) {
47 | throw new IllegalArgumentException(
48 | "Mandatory " + Attributes.Name.MANIFEST_VERSION + " attribute missing");
49 | }
50 | writeAttribute(out, Attributes.Name.MANIFEST_VERSION, manifestVersion);
51 |
52 | if (attributes.size() > 1) {
53 | SortedMap namedAttributes = getAttributesSortedByName(attributes);
54 | namedAttributes.remove(Attributes.Name.MANIFEST_VERSION.toString());
55 | writeAttributes(out, namedAttributes);
56 | }
57 | writeSectionDelimiter(out);
58 | }
59 |
60 | public static void writeIndividualSection(OutputStream out, String name, Attributes attributes)
61 | throws IOException {
62 | writeAttribute(out, "Name", name);
63 |
64 | if (!attributes.isEmpty()) {
65 | writeAttributes(out, getAttributesSortedByName(attributes));
66 | }
67 | writeSectionDelimiter(out);
68 | }
69 |
70 | static void writeSectionDelimiter(OutputStream out) throws IOException {
71 | out.write(CRLF);
72 | }
73 |
74 | static void writeAttribute(OutputStream out, Attributes.Name name, String value)
75 | throws IOException {
76 | writeAttribute(out, name.toString(), value);
77 | }
78 |
79 | private static void writeAttribute(OutputStream out, String name, String value)
80 | throws IOException {
81 | writeLine(out, name + ": " + value);
82 | }
83 |
84 | private static void writeLine(OutputStream out, String line) throws IOException {
85 | byte[] lineBytes = line.getBytes(StandardCharsets.UTF_8);
86 | int offset = 0;
87 | int remaining = lineBytes.length;
88 | boolean firstLine = true;
89 | while (remaining > 0) {
90 | int chunkLength;
91 | if (firstLine) {
92 | // First line
93 | chunkLength = Math.min(remaining, MAX_LINE_LENGTH);
94 | } else {
95 | // Continuation line
96 | out.write(CRLF);
97 | out.write(' ');
98 | chunkLength = Math.min(remaining, MAX_LINE_LENGTH - 1);
99 | }
100 | out.write(lineBytes, offset, chunkLength);
101 | offset += chunkLength;
102 | remaining -= chunkLength;
103 | firstLine = false;
104 | }
105 | out.write(CRLF);
106 | }
107 |
108 | static SortedMap getAttributesSortedByName(Attributes attributes) {
109 | Set> attributesEntries = attributes.entrySet();
110 | SortedMap namedAttributes = new TreeMap();
111 | for (Map.Entry