Add zip hint generation support to signapk tool
Test: unzip -q -c myapp.apk.signed pinlist.meta | od --endian=big -w8 -tx4 Bug: 79259761 Bug: 65316207 Change-Id: I71c01ac24e93afe75f60697a9849e1dd35e1b49d
This commit is contained in:
parent
1defe63536
commit
334ece99dd
2 changed files with 132 additions and 4 deletions
|
@ -0,0 +1,59 @@
|
|||
/*
|
||||
* Copyright (C) 2018 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package com.android.signapk;
|
||||
import java.io.OutputStream;
|
||||
import java.io.IOException;
|
||||
|
||||
class CountingOutputStream extends OutputStream {
|
||||
private final OutputStream mBase;
|
||||
private long mWrittenBytes;
|
||||
|
||||
public CountingOutputStream(OutputStream base) {
|
||||
mBase = base;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() throws IOException {
|
||||
mBase.close();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void flush() throws IOException {
|
||||
mBase.flush();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void write(byte[] b) throws IOException {
|
||||
mBase.write(b);
|
||||
mWrittenBytes += b.length;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void write(byte[] b, int off, int len) throws IOException {
|
||||
mBase.write(b, off, len);
|
||||
mWrittenBytes += len;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void write(int b) throws IOException {
|
||||
mBase.write(b);
|
||||
mWrittenBytes += 1;
|
||||
}
|
||||
|
||||
public long getWrittenBytes() {
|
||||
return mWrittenBytes;
|
||||
}
|
||||
}
|
|
@ -36,6 +36,7 @@ import org.conscrypt.OpenSSLProvider;
|
|||
|
||||
import com.android.apksig.ApkSignerEngine;
|
||||
import com.android.apksig.DefaultApkSignerEngine;
|
||||
import com.android.apksig.Hints;
|
||||
import com.android.apksig.apk.ApkUtils;
|
||||
import com.android.apksig.apk.MinSdkVersionException;
|
||||
import com.android.apksig.util.DataSink;
|
||||
|
@ -73,6 +74,7 @@ import java.security.spec.PKCS8EncodedKeySpec;
|
|||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.Enumeration;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
import java.util.TimeZone;
|
||||
|
@ -80,6 +82,7 @@ import java.util.jar.JarEntry;
|
|||
import java.util.jar.JarFile;
|
||||
import java.util.jar.JarOutputStream;
|
||||
import java.util.regex.Pattern;
|
||||
import java.util.zip.ZipEntry;
|
||||
|
||||
import javax.crypto.Cipher;
|
||||
import javax.crypto.EncryptedPrivateKeyInfo;
|
||||
|
@ -372,11 +375,16 @@ class SignApk {
|
|||
Pattern ignoredFilenamePattern,
|
||||
ApkSignerEngine apkSigner,
|
||||
JarOutputStream out,
|
||||
CountingOutputStream outCounter,
|
||||
long timestamp,
|
||||
int defaultAlignment) throws IOException {
|
||||
byte[] buffer = new byte[4096];
|
||||
int num;
|
||||
|
||||
List<Pattern> pinPatterns = extractPinPatterns(in);
|
||||
ArrayList<Hints.ByteRange> pinByteRanges = pinPatterns == null ? null : new ArrayList<>();
|
||||
HashSet<String> namesToPin = new HashSet<>();
|
||||
|
||||
ArrayList<String> names = new ArrayList<String>();
|
||||
for (Enumeration<JarEntry> e = in.entries(); e.hasMoreElements();) {
|
||||
JarEntry entry = e.nextElement();
|
||||
|
@ -388,6 +396,16 @@ class SignApk {
|
|||
&& (ignoredFilenamePattern.matcher(entryName).matches())) {
|
||||
continue;
|
||||
}
|
||||
if (Hints.PIN_BYTE_RANGE_ZIP_ENTRY_NAME.equals(entryName)) {
|
||||
continue; // We regenerate it below.
|
||||
}
|
||||
if (pinPatterns != null) {
|
||||
for (Pattern pinPattern : pinPatterns) {
|
||||
if (pinPattern.matcher(entryName).matches()) {
|
||||
namesToPin.add(entryName);
|
||||
}
|
||||
}
|
||||
}
|
||||
names.add(entryName);
|
||||
}
|
||||
Collections.sort(names);
|
||||
|
@ -460,6 +478,7 @@ class SignApk {
|
|||
outEntry.setExtra(extra);
|
||||
offset += extra.length;
|
||||
|
||||
long entryHeaderStart = outCounter.getWrittenBytes();
|
||||
out.putNextEntry(outEntry);
|
||||
ApkSignerEngine.InspectJarEntryRequest inspectEntryRequest =
|
||||
(apkSigner != null) ? apkSigner.outputJarEntry(name) : null;
|
||||
|
@ -475,10 +494,18 @@ class SignApk {
|
|||
offset += num;
|
||||
}
|
||||
}
|
||||
out.closeEntry();
|
||||
out.flush();
|
||||
if (inspectEntryRequest != null) {
|
||||
inspectEntryRequest.done();
|
||||
}
|
||||
|
||||
if (namesToPin.contains(name)) {
|
||||
pinByteRanges.add(
|
||||
new Hints.ByteRange(
|
||||
entryHeaderStart,
|
||||
outCounter.getWrittenBytes()));
|
||||
}
|
||||
}
|
||||
|
||||
// Copy all the non-STORED entries. We don't attempt to
|
||||
|
@ -494,6 +521,7 @@ class SignApk {
|
|||
// Create a new entry so that the compressed len is recomputed.
|
||||
JarEntry outEntry = new JarEntry(name);
|
||||
outEntry.setTime(timestamp);
|
||||
long entryHeaderStart = outCounter.getWrittenBytes();
|
||||
out.putNextEntry(outEntry);
|
||||
ApkSignerEngine.InspectJarEntryRequest inspectEntryRequest =
|
||||
(apkSigner != null) ? apkSigner.outputJarEntry(name) : null;
|
||||
|
@ -507,13 +535,49 @@ class SignApk {
|
|||
entryDataSink.consume(buffer, 0, num);
|
||||
}
|
||||
}
|
||||
out.closeEntry();
|
||||
out.flush();
|
||||
if (inspectEntryRequest != null) {
|
||||
inspectEntryRequest.done();
|
||||
}
|
||||
|
||||
if (namesToPin.contains(name)) {
|
||||
pinByteRanges.add(
|
||||
new Hints.ByteRange(
|
||||
entryHeaderStart,
|
||||
outCounter.getWrittenBytes()));
|
||||
}
|
||||
}
|
||||
|
||||
if (pinByteRanges != null) {
|
||||
// Cover central directory
|
||||
pinByteRanges.add(
|
||||
new Hints.ByteRange(outCounter.getWrittenBytes(),
|
||||
Long.MAX_VALUE));
|
||||
addPinByteRanges(out, pinByteRanges, timestamp);
|
||||
}
|
||||
}
|
||||
|
||||
private static List<Pattern> extractPinPatterns(JarFile in) throws IOException {
|
||||
ZipEntry pinMetaEntry = in.getEntry(Hints.PIN_HINT_ASSET_ZIP_ENTRY_NAME);
|
||||
if (pinMetaEntry == null) {
|
||||
return null;
|
||||
}
|
||||
InputStream pinMetaStream = in.getInputStream(pinMetaEntry);
|
||||
byte[] patternBlob = new byte[(int) pinMetaEntry.getSize()];
|
||||
pinMetaStream.read(patternBlob);
|
||||
return Hints.parsePinPatterns(patternBlob);
|
||||
}
|
||||
|
||||
private static void addPinByteRanges(JarOutputStream outputJar,
|
||||
ArrayList<Hints.ByteRange> pinByteRanges,
|
||||
long timestamp) throws IOException {
|
||||
JarEntry je = new JarEntry(Hints.PIN_BYTE_RANGE_ZIP_ENTRY_NAME);
|
||||
je.setTime(timestamp);
|
||||
outputJar.putNextEntry(je);
|
||||
outputJar.write(Hints.encodeByteRangeList(pinByteRanges));
|
||||
}
|
||||
|
||||
private static boolean shouldOutputApkEntry(
|
||||
ApkSignerEngine apkSigner, JarFile inFile, JarEntry inEntry, byte[] tmpbuf)
|
||||
throws IOException {
|
||||
|
@ -679,9 +743,11 @@ class SignApk {
|
|||
public void write(OutputStream out) throws IOException {
|
||||
try {
|
||||
signer = new WholeFileSignerOutputStream(out, outputStream);
|
||||
JarOutputStream outputJar = new JarOutputStream(signer);
|
||||
CountingOutputStream outputJarCounter = new CountingOutputStream(signer);
|
||||
JarOutputStream outputJar = new JarOutputStream(outputJarCounter);
|
||||
|
||||
copyFiles(inputJar, STRIP_PATTERN, null, outputJar, timestamp, 0);
|
||||
copyFiles(inputJar, STRIP_PATTERN, null, outputJar,
|
||||
outputJarCounter, timestamp, 0);
|
||||
addOtacert(outputJar, publicKeyFile, timestamp);
|
||||
|
||||
signer.notifyClosing();
|
||||
|
@ -1065,11 +1131,14 @@ class SignApk {
|
|||
// Build the output APK in memory, by copying input APK's ZIP entries across
|
||||
// and then signing the output APK.
|
||||
ByteArrayOutputStream v1SignedApkBuf = new ByteArrayOutputStream();
|
||||
JarOutputStream outputJar = new JarOutputStream(v1SignedApkBuf);
|
||||
CountingOutputStream outputJarCounter =
|
||||
new CountingOutputStream(v1SignedApkBuf);
|
||||
JarOutputStream outputJar = new JarOutputStream(outputJarCounter);
|
||||
// Use maximum compression for compressed entries because the APK lives forever
|
||||
// on the system partition.
|
||||
outputJar.setLevel(9);
|
||||
copyFiles(inputJar, null, apkSigner, outputJar, timestamp, alignment);
|
||||
copyFiles(inputJar, null, apkSigner, outputJar,
|
||||
outputJarCounter, timestamp, alignment);
|
||||
ApkSignerEngine.OutputJarSignatureRequest addV1SignatureRequest =
|
||||
apkSigner.outputJarEntries();
|
||||
if (addV1SignatureRequest != null) {
|
||||
|
|
Loading…
Reference in a new issue