use of com.android.apksig.util.DataSink in project apksig by venshine.
the class VerityTreeBuilder method generateVerityTree.
/**
* Returns the byte buffer that contains the whole verity tree.
*
* The tree is built bottom up. The bottom level has 256-bit digest for each 4 KB block in the
* input file. If the total size is larger than 4 KB, take this level as input and repeat the
* same procedure, until the level is within 4 KB. If salt is given, it will apply to each
* digestion before the actual data.
*
* The returned root hash is calculated from the last level of 4 KB chunk, similarly with salt.
*
* The tree is currently stored only in memory and is never written out. Nevertheless, it is
* the actual verity tree format on disk, and is supposed to be re-generated on device.
*/
public ByteBuffer generateVerityTree(DataSource fileSource) throws IOException {
int digestSize = mMd.getDigestLength();
// Calculate the summed area table of level size. In other word, this is the offset
// table of each level, plus the next non-existing level.
int[] levelOffset = calculateLevelOffset(fileSource.size(), digestSize);
ByteBuffer verityBuffer = ByteBuffer.allocate(levelOffset[levelOffset.length - 1]);
// Generate the hash tree bottom-up.
for (int i = levelOffset.length - 2; i >= 0; i--) {
DataSink middleBufferSink = new ByteBufferSink(slice(verityBuffer, levelOffset[i], levelOffset[i + 1]));
DataSource src;
if (i == levelOffset.length - 2) {
src = fileSource;
digestDataByChunks(src, middleBufferSink);
} else {
src = DataSources.asDataSource(slice(verityBuffer.asReadOnlyBuffer(), levelOffset[i + 1], levelOffset[i + 2]));
digestDataByChunks(src, middleBufferSink);
}
// If the output is not full chunk, pad with 0s.
long totalOutput = divideRoundup(src.size(), CHUNK_SIZE) * digestSize;
int incomplete = (int) (totalOutput % CHUNK_SIZE);
if (incomplete > 0) {
byte[] padding = new byte[CHUNK_SIZE - incomplete];
middleBufferSink.consume(padding, 0, padding.length);
}
}
return verityBuffer;
}
use of com.android.apksig.util.DataSink in project apksig by venshine.
the class ApkSigner method sign.
/**
* Signs the input APK and outputs the resulting signed APK. The input APK is not modified.
*
* @throws IOException if an I/O error is encountered while reading or writing the APKs
* @throws ApkFormatException if the input APK is malformed
* @throws NoSuchAlgorithmException if the APK signatures cannot be produced or verified because
* a required cryptographic algorithm implementation is missing
* @throws InvalidKeyException if a signature could not be generated because a signing key is
* not suitable for generating the signature
* @throws SignatureException if an error occurred while generating or verifying a signature
* @throws IllegalStateException if this signer's configuration is missing required information
* or if the signing engine is in an invalid state.
*/
public void sign() throws IOException, ApkFormatException, NoSuchAlgorithmException, InvalidKeyException, SignatureException, IllegalStateException {
Closeable in = null;
DataSource inputApk;
try {
if (mInputApkDataSource != null) {
inputApk = mInputApkDataSource;
} else if (mInputApkFile != null) {
RandomAccessFile inputFile = new RandomAccessFile(mInputApkFile, "r");
in = inputFile;
inputApk = DataSources.asDataSource(inputFile);
} else {
throw new IllegalStateException("Input APK not specified");
}
Closeable out = null;
try {
DataSink outputApkOut;
DataSource outputApkIn;
if (mOutputApkDataSink != null) {
outputApkOut = mOutputApkDataSink;
outputApkIn = mOutputApkDataSource;
} else if (mOutputApkFile != null) {
RandomAccessFile outputFile = new RandomAccessFile(mOutputApkFile, "rw");
out = outputFile;
outputFile.setLength(0);
outputApkOut = DataSinks.asDataSink(outputFile);
outputApkIn = DataSources.asDataSource(outputFile);
} else {
throw new IllegalStateException("Output APK not specified");
}
sign(inputApk, outputApkOut, outputApkIn);
} finally {
if (out != null) {
out.close();
}
}
} finally {
if (in != null) {
in.close();
}
}
}
use of com.android.apksig.util.DataSink in project apksig by venshine.
the class ApkSigningBlockUtils method computeOneMbChunkContentDigests.
static void computeOneMbChunkContentDigests(Set<ContentDigestAlgorithm> digestAlgorithms, DataSource[] contents, Map<ContentDigestAlgorithm, byte[]> outputContentDigests) throws IOException, NoSuchAlgorithmException, DigestException {
// For each digest algorithm the result is computed as follows:
// 1. Each segment of contents is split into consecutive chunks of 1 MB in size.
// The final chunk will be shorter iff the length of segment is not a multiple of 1 MB.
// No chunks are produced for empty (zero length) segments.
// 2. The digest of each chunk is computed over the concatenation of byte 0xa5, the chunk's
// length in bytes (uint32 little-endian) and the chunk's contents.
// 3. The output digest is computed over the concatenation of the byte 0x5a, the number of
// chunks (uint32 little-endian) and the concatenation of digests of chunks of all
// segments in-order.
long chunkCountLong = 0;
for (DataSource input : contents) {
chunkCountLong += getChunkCount(input.size(), CONTENT_DIGESTED_CHUNK_MAX_SIZE_BYTES);
}
if (chunkCountLong > Integer.MAX_VALUE) {
throw new DigestException("Input too long: " + chunkCountLong + " chunks");
}
int chunkCount = (int) chunkCountLong;
ContentDigestAlgorithm[] digestAlgorithmsArray = digestAlgorithms.toArray(new ContentDigestAlgorithm[digestAlgorithms.size()]);
MessageDigest[] mds = new MessageDigest[digestAlgorithmsArray.length];
byte[][] digestsOfChunks = new byte[digestAlgorithmsArray.length][];
int[] digestOutputSizes = new int[digestAlgorithmsArray.length];
for (int i = 0; i < digestAlgorithmsArray.length; i++) {
ContentDigestAlgorithm digestAlgorithm = digestAlgorithmsArray[i];
int digestOutputSizeBytes = digestAlgorithm.getChunkDigestOutputSizeBytes();
digestOutputSizes[i] = digestOutputSizeBytes;
byte[] concatenationOfChunkCountAndChunkDigests = new byte[5 + chunkCount * digestOutputSizeBytes];
concatenationOfChunkCountAndChunkDigests[0] = 0x5a;
setUnsignedInt32LittleEndian(chunkCount, concatenationOfChunkCountAndChunkDigests, 1);
digestsOfChunks[i] = concatenationOfChunkCountAndChunkDigests;
String jcaAlgorithm = digestAlgorithm.getJcaMessageDigestAlgorithm();
mds[i] = MessageDigest.getInstance(jcaAlgorithm);
}
DataSink mdSink = DataSinks.asDataSink(mds);
byte[] chunkContentPrefix = new byte[5];
chunkContentPrefix[0] = (byte) 0xa5;
int chunkIndex = 0;
// digest to be more efficient because it's presented with all of its input in one go.
for (DataSource input : contents) {
long inputOffset = 0;
long inputRemaining = input.size();
while (inputRemaining > 0) {
int chunkSize = (int) Math.min(inputRemaining, CONTENT_DIGESTED_CHUNK_MAX_SIZE_BYTES);
setUnsignedInt32LittleEndian(chunkSize, chunkContentPrefix, 1);
for (int i = 0; i < mds.length; i++) {
mds[i].update(chunkContentPrefix);
}
try {
input.feed(inputOffset, chunkSize, mdSink);
} catch (IOException e) {
throw new IOException("Failed to read chunk #" + chunkIndex, e);
}
for (int i = 0; i < digestAlgorithmsArray.length; i++) {
MessageDigest md = mds[i];
byte[] concatenationOfChunkCountAndChunkDigests = digestsOfChunks[i];
int expectedDigestSizeBytes = digestOutputSizes[i];
int actualDigestSizeBytes = md.digest(concatenationOfChunkCountAndChunkDigests, 5 + chunkIndex * expectedDigestSizeBytes, expectedDigestSizeBytes);
if (actualDigestSizeBytes != expectedDigestSizeBytes) {
throw new RuntimeException("Unexpected output size of " + md.getAlgorithm() + " digest: " + actualDigestSizeBytes);
}
}
inputOffset += chunkSize;
inputRemaining -= chunkSize;
chunkIndex++;
}
}
for (int i = 0; i < digestAlgorithmsArray.length; i++) {
ContentDigestAlgorithm digestAlgorithm = digestAlgorithmsArray[i];
byte[] concatenationOfChunkCountAndChunkDigests = digestsOfChunks[i];
MessageDigest md = mds[i];
byte[] digest = md.digest(concatenationOfChunkCountAndChunkDigests);
outputContentDigests.put(digestAlgorithm, digest);
}
}
Aggregations