Merge "APK Signature Scheme v2 APK verifier."

This commit is contained in:
Alex Klyubin
2016-06-08 20:51:58 +00:00
committed by Gerrit Code Review
9 changed files with 1586 additions and 13 deletions

View File

@@ -0,0 +1,458 @@
/*
* Copyright (C) 2016 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.apksigner.core;
import com.android.apksigner.core.apk.ApkUtils;
import com.android.apksigner.core.internal.apk.v2.ContentDigestAlgorithm;
import com.android.apksigner.core.internal.apk.v2.SignatureAlgorithm;
import com.android.apksigner.core.internal.apk.v2.V2SchemeVerifier;
import com.android.apksigner.core.util.DataSource;
import com.android.apksigner.core.zip.ZipFormatException;
import java.io.IOException;
import java.security.cert.X509Certificate;
import java.util.ArrayList;
import java.util.List;
/**
* APK signature verifier which mimics the behavior of the Android platform.
*
* <p>The verifier is designed to closely mimic the behavior of Android platforms. This is to enable
* the verifier to be used for checking whether an APK's signatures will verify on Android.
*/
public class ApkVerifier {
/**
* Verifies the APK's signatures and returns the result of verification. The APK can be
* considered verified iff the result's {@link Result#isVerified()} returns {@code true}.
* The verification result also includes errors, warnings, and information about signers.
*
* @param apk APK file contents
* @param minSdkVersion API Level of the oldest Android platform on which the APK's signatures
* may need to be verified
*
* @throws IOException if an I/O error is encountered while reading the APK
* @throws ZipFormatException if the APK is malformed at ZIP format level
*/
public Result verify(DataSource apk, int minSdkVersion) throws IOException, ZipFormatException {
ApkUtils.ZipSections zipSections = ApkUtils.findZipSections(apk);
// Attempt to verify the APK using APK Signature Scheme v2
Result result = new Result();
try {
V2SchemeVerifier.Result v2Result = V2SchemeVerifier.verify(apk, zipSections);
result.mergeFrom(v2Result);
} catch (V2SchemeVerifier.SignatureNotFoundException ignored) {}
if (result.containsErrors()) {
return result;
}
// TODO: Verify JAR signature if necessary
if (!result.isVerifiedUsingV2Scheme()) {
return result;
}
// Verified
result.setVerified();
for (Result.V2SchemeSignerInfo signerInfo : result.getV2SchemeSigners()) {
result.addSignerCertificate(signerInfo.getCertificate());
}
return result;
}
/**
* Result of verifying an APKs signatures. The APK can be considered verified iff
* {@link #isVerified()} returns {@code true}.
*/
public static class Result {
private final List<IssueWithParams> mErrors = new ArrayList<>();
private final List<IssueWithParams> mWarnings = new ArrayList<>();
private final List<X509Certificate> mSignerCerts = new ArrayList<>();
private final List<V2SchemeSignerInfo> mV2SchemeSigners = new ArrayList<>();
private boolean mVerified;
private boolean mVerifiedUsingV2Scheme;
/**
* Returns {@code true} if the APK's signatures verified.
*/
public boolean isVerified() {
return mVerified;
}
private void setVerified() {
mVerified = true;
}
/**
* Returns {@code true} if the APK's APK Signature Scheme v2 signatures verified.
*/
public boolean isVerifiedUsingV2Scheme() {
return mVerifiedUsingV2Scheme;
}
/**
* Returns the verified signers' certificates, one per signer.
*/
public List<X509Certificate> getSignerCertificates() {
return mSignerCerts;
}
private void addSignerCertificate(X509Certificate cert) {
mSignerCerts.add(cert);
}
/**
* Returns information about APK Signature Scheme v2 signers associated with the APK's
* signature.
*/
public List<V2SchemeSignerInfo> getV2SchemeSigners() {
return mV2SchemeSigners;
}
/**
* Returns errors encountered while verifying the APK's signatures.
*/
public List<IssueWithParams> getErrors() {
return mErrors;
}
/**
* Returns warnings encountered while verifying the APK's signatures.
*/
public List<IssueWithParams> getWarnings() {
return mWarnings;
}
private void mergeFrom(V2SchemeVerifier.Result source) {
mVerifiedUsingV2Scheme = source.verified;
mErrors.addAll(source.getErrors());
mWarnings.addAll(source.getWarnings());
for (V2SchemeVerifier.Result.SignerInfo signer : source.signers) {
mV2SchemeSigners.add(new V2SchemeSignerInfo(signer));
}
}
/**
* Returns {@code true} if an error was encountered while verifying the APK. Any error
* prevents the APK from being considered verified.
*/
public boolean containsErrors() {
if (!mErrors.isEmpty()) {
return true;
}
if (!mV2SchemeSigners.isEmpty()) {
for (V2SchemeSignerInfo signer : mV2SchemeSigners) {
if (signer.containsErrors()) {
return true;
}
}
}
return false;
}
/**
* Information about an APK Signature Scheme v2 signer associated with the APK's signature.
*/
public static class V2SchemeSignerInfo {
private final int mIndex;
private final List<X509Certificate> mCerts;
private final List<IssueWithParams> mErrors;
private final List<IssueWithParams> mWarnings;
private V2SchemeSignerInfo(V2SchemeVerifier.Result.SignerInfo result) {
mIndex = result.index;
mCerts = result.certs;
mErrors = result.getErrors();
mWarnings = result.getWarnings();
}
/**
* Returns this signer's {@code 0}-based index in the list of signers contained in the
* APK's APK Signature Scheme v2 signature.
*/
public int getIndex() {
return mIndex;
}
/**
* Returns this signer's signing certificate or {@code null} if not available. The
* certificate is guaranteed to be available if no errors were encountered during
* verification (see {@link #containsErrors()}.
*
* <p>This certificate contains the signer's public key.
*/
public X509Certificate getCertificate() {
return mCerts.isEmpty() ? null : mCerts.get(0);
}
/**
* Returns this signer's certificates. The first certificate is for the signer's public
* key. An empty list may be returned if an error was encountered during verification
* (see {@link #containsErrors()}).
*/
public List<X509Certificate> getCertificates() {
return mCerts;
}
public boolean containsErrors() {
return !mErrors.isEmpty();
}
public List<IssueWithParams> getErrors() {
return mErrors;
}
public List<IssueWithParams> getWarnings() {
return mWarnings;
}
}
}
/**
* Error or warning encountered while verifying an APK's signatures.
*/
public static enum Issue {
/**
* Failed to parse the list of signers contained in the APK Signature Scheme v2 signature.
*/
V2_SIG_MALFORMED_SIGNERS("Malformed list of signers"),
/**
* Failed to parse this signer's signer block contained in the APK Signature Scheme v2
* signature.
*/
V2_SIG_MALFORMED_SIGNER("Malformed signer block"),
/**
* Public key embedded in the APK Signature Scheme v2 signature of this signer could not be
* parsed.
*
* <ul>
* <li>Parameter 1: error details ({@code Throwable})</li>
* </ul>
*/
V2_SIG_MALFORMED_PUBLIC_KEY("Malformed public key: %1$s"),
/**
* This APK Signature Scheme v2 signer's certificate could not be parsed.
*
* <ul>
* <li>Parameter 1: index ({@code 0}-based) of the certificate in the signer's list of
* certificates ({@code Integer})</li>
* <li>Parameter 2: sequence number ({@code 1}-based) of the certificate in the signer's
* list of certificates ({@code Integer})</li>
* <li>Parameter 3: error details ({@code Throwable})</li>
* </ul>
*/
V2_SIG_MALFORMED_CERTIFICATE("Malformed certificate #%2$d: %3$s"),
/**
* Failed to parse this signer's signature record contained in the APK Signature Scheme v2
* signature.
*
* <ul>
* <li>Parameter 1: record number (first record is {@code 1}) ({@code Integer})</li>
* </ul>
*/
V2_SIG_MALFORMED_SIGNATURE("Malformed APK Signature Scheme v2 signature record #%1$d"),
/**
* Failed to parse this signer's digest record contained in the APK Signature Scheme v2
* signature.
*
* <ul>
* <li>Parameter 1: record number (first record is {@code 1}) ({@code Integer})</li>
* </ul>
*/
V2_SIG_MALFORMED_DIGEST("Malformed APK Signature Scheme v2 digest record #%1$d"),
/**
* This APK Signature Scheme v2 signer contains a malformed additional attribute.
*
* <ul>
* <li>Parameter 1: attribute number (first attribute is {@code 1}) {@code Integer})</li>
* </ul>
*/
V2_SIG_MALFORMED_ADDITIONAL_ATTRIBUTE("Malformed additional attribute #%1$d"),
/**
* APK Signature Scheme v2 signature contains no signers.
*/
V2_SIG_NO_SIGNERS("No signers in APK Signature Scheme v2 signature"),
/**
* This APK Signature Scheme v2 signer contains a signature produced using an unknown
* algorithm.
*
* <ul>
* <li>Parameter 1: algorithm ID ({@code Integer})</li>
* </ul>
*/
V2_SIG_UNKNOWN_SIG_ALGORITHM("Unknown signature algorithm: %1$#x"),
/**
* This APK Signature Scheme v2 signer contains an unknown additional attribute.
*
* <ul>
* <li>Parameter 1: attribute ID ({@code Integer})</li>
* </ul>
*/
V2_SIG_UNKNOWN_ADDITIONAL_ATTRIBUTE("Unknown additional attribute: ID %1$#x"),
/**
* An exception was encountered while verifying APK Signature Scheme v2 signature of this
* signer.
*
* <ul>
* <li>Parameter 1: signature algorithm ({@link SignatureAlgorithm})</li>
* <li>Parameter 2: exception ({@code Throwable})</li>
* </ul>
*/
V2_SIG_VERIFY_EXCEPTION("Failed to verify %1$s signature: %2$s"),
/**
* APK Signature Scheme v2 signature over this signer's signed-data block did not verify.
*
* <ul>
* <li>Parameter 1: signature algorithm ({@link SignatureAlgorithm})</li>
* </ul>
*/
V2_SIG_DID_NOT_VERIFY("%1$s signature over signed-data did not verify"),
/**
* This APK Signature Scheme v2 signer offers no signatures.
*/
V2_SIG_NO_SIGNATURES("No signatures"),
/**
* This APK Signature Scheme v2 signer offers signatures but none of them are supported.
*/
V2_SIG_NO_SUPPORTED_SIGNATURES("No supported signatures"),
/**
* This APK Signature Scheme v2 signer offers no certificates.
*/
V2_SIG_NO_CERTIFICATES("No certificates"),
/**
* This APK Signature Scheme v2 signer's public key listed in the signer's certificate does
* not match the public key listed in the signatures record.
*
* <ul>
* <li>Parameter 1: hex-encoded public key from certificate ({@code String})</li>
* <li>Parameter 2: hex-encoded public key from signatures record ({@code String})</li>
* </ul>
*/
V2_SIG_PUBLIC_KEY_MISMATCH_BETWEEN_CERTIFICATE_AND_SIGNATURES_RECORD(
"Public key mismatch between certificate and signature record: <%1$s> vs <%2$s>"),
/**
* This APK Signature Scheme v2 signer's signature algorithms listed in the signatures
* record do not match the signature algorithms listed in the signatures record.
*
* <ul>
* <li>Parameter 1: signature algorithms from signatures record ({@code List<Integer>})</li>
* <li>Parameter 2: signature algorithms from digests record ({@code List<Integer>})</li>
* </ul>
*/
V2_SIG_SIG_ALG_MISMATCH_BETWEEN_SIGNATURES_AND_DIGESTS_RECORDS(
"Signature algorithms mismatch between signatures and digests records"
+ ": %1$s vs %2$s"),
/**
* The APK's digest does not match the digest contained in the APK Signature Scheme v2
* signature.
*
* <ul>
* <li>Parameter 1: content digest algorithm ({@link ContentDigestAlgorithm})</li>
* <li>Parameter 2: hex-encoded expected digest of the APK ({@code String})</li>
* <li>Parameter 3: hex-encoded actual digest of the APK ({@code String})</li>
* </ul>
*/
V2_SIG_APK_DIGEST_DID_NOT_VERIFY(
"APK integrity check failed. %1$s digest mismatch."
+ " Expected: <%2$s>, actual: <%3$s>"),
/**
* APK Signing Block contains an unknown entry.
*
* <ul>
* <li>Parameter 1: entry ID ({@code Integer})</li>
* </ul>
*/
APK_SIG_BLOCK_UNKNOWN_ENTRY_ID("APK Signing Block contains unknown entry: ID %1$#x");
private final String mFormat;
private Issue(String format) {
mFormat = format;
}
/**
* Returns the format string suitable for combining the parameters of this issue into a
* readable string. See {@link java.util.Formatter} for format.
*/
private String getFormat() {
return mFormat;
}
}
/**
* {@link Issue} with associated parameters. {@link #toString()} produces a readable formatted
* form.
*/
public static class IssueWithParams {
private final Issue mIssue;
private final Object[] mParams;
/**
* Constructs a new {@code IssueWithParams} of the specified type and with provided
* parameters.
*/
public IssueWithParams(Issue issue, Object[] params) {
mIssue = issue;
mParams = params;
}
/**
* Returns the type of this issue.
*/
public Issue getIssue() {
return mIssue;
}
/**
* Returns the parameters of this issue.
*/
public Object[] getParams() {
return mParams.clone();
}
/**
* Returns a readable form of this issue.
*/
@Override
public String toString() {
return String.format(mIssue.getFormat(), mParams);
}
}
}

View File

@@ -18,9 +18,9 @@ package com.android.apksigner.core;
import com.android.apksigner.core.internal.apk.v1.DigestAlgorithm;
import com.android.apksigner.core.internal.apk.v1.V1SchemeSigner;
import com.android.apksigner.core.internal.apk.v2.MessageDigestSink;
import com.android.apksigner.core.internal.apk.v2.V2SchemeSigner;
import com.android.apksigner.core.internal.util.ByteArrayOutputStreamSink;
import com.android.apksigner.core.internal.util.MessageDigestSink;
import com.android.apksigner.core.internal.util.Pair;
import com.android.apksigner.core.util.DataSink;
import com.android.apksigner.core.util.DataSource;

View File

@@ -19,7 +19,7 @@ package com.android.apksigner.core.internal.apk.v2;
/**
* APK Signature Scheme v2 content digest algorithm.
*/
enum ContentDigestAlgorithm {
public enum ContentDigestAlgorithm {
/** SHA2-256 over 1 MB chunks. */
CHUNKED_SHA256("SHA-256", 256 / 8),

View File

@@ -23,7 +23,7 @@ import java.security.spec.MGF1ParameterSpec;
import java.security.spec.PSSParameterSpec;
/**
* APK Signature Scheme v2 content digest algorithm.
* APK Signature Scheme v2 signature algorithm.
*/
public enum SignatureAlgorithm {
/**

View File

@@ -16,6 +16,7 @@
package com.android.apksigner.core.internal.apk.v2;
import com.android.apksigner.core.internal.util.MessageDigestSink;
import com.android.apksigner.core.internal.util.Pair;
import com.android.apksigner.core.internal.zip.ZipUtils;
import com.android.apksigner.core.util.DataSource;
@@ -216,7 +217,7 @@ public abstract class V2SchemeSigner {
return generateApkSigningBlock(signerConfigs, contentDigests);
}
private static Map<ContentDigestAlgorithm, byte[]> computeContentDigests(
static Map<ContentDigestAlgorithm, byte[]> computeContentDigests(
Set<ContentDigestAlgorithm> digestAlgorithms,
DataSource[] contents) throws IOException, DigestException {
// For each digest algorithm the result is computed as follows:

View File

@@ -0,0 +1,939 @@
/*
* Copyright (C) 2016 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.apksigner.core.internal.apk.v2;
import com.android.apksigner.core.ApkVerifier.Issue;
import com.android.apksigner.core.ApkVerifier.IssueWithParams;
import com.android.apksigner.core.apk.ApkUtils;
import com.android.apksigner.core.internal.util.ByteBufferDataSource;
import com.android.apksigner.core.internal.util.DelegatingX509Certificate;
import com.android.apksigner.core.internal.util.Pair;
import com.android.apksigner.core.internal.zip.ZipUtils;
import com.android.apksigner.core.util.DataSource;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.nio.BufferUnderflowException;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.security.DigestException;
import java.security.KeyFactory;
import java.security.PublicKey;
import java.security.Signature;
import java.security.cert.CertificateEncodingException;
import java.security.cert.CertificateException;
import java.security.cert.CertificateFactory;
import java.security.cert.X509Certificate;
import java.security.spec.AlgorithmParameterSpec;
import java.security.spec.X509EncodedKeySpec;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
/**
* APK Signature Scheme v2 verifier.
*
* <p>APK Signature Scheme v2 is a whole-file signature scheme which aims to protect every single
* bit of the APK, as opposed to the JAR Signature Scheme which protects only the names and
* uncompressed contents of ZIP entries.
*
* <p>TODO: Link to APK Signature Scheme v2 documentation once it's available.
*/
public abstract class V2SchemeVerifier {
private static final long APK_SIG_BLOCK_MAGIC_HI = 0x3234206b636f6c42L;
private static final long APK_SIG_BLOCK_MAGIC_LO = 0x20676953204b5041L;
private static final int APK_SIG_BLOCK_MIN_SIZE = 32;
private static final int APK_SIGNATURE_SCHEME_V2_BLOCK_ID = 0x7109871a;
/** Hidden constructor to prevent instantiation. */
private V2SchemeVerifier() {}
/**
* Verifies the provided APK's APK Signature Scheme v2 signatures and returns the result of
* verification. APK is considered verified only if {@link Result#verified} is {@code true}. If
* verification fails, the result will contain errors -- see {@link Result#getErrors()}.
*
* @throws SignatureNotFoundException if no APK Signature Scheme v2 signatures are found
* @throws IOException if an I/O error occurs when reading the APK
*/
public static Result verify(DataSource apk, ApkUtils.ZipSections zipSections)
throws IOException, SignatureNotFoundException {
Result result = new Result();
SignatureInfo signatureInfo = findSignature(apk, zipSections, result);
DataSource beforeApkSigningBlock = apk.slice(0, signatureInfo.apkSigningBlockOffset);
DataSource centralDir =
apk.slice(
signatureInfo.centralDirOffset,
signatureInfo.eocdOffset - signatureInfo.centralDirOffset);
ByteBuffer eocd = signatureInfo.eocd;
verify(beforeApkSigningBlock,
signatureInfo.signatureBlock,
centralDir,
eocd,
result);
return result;
}
/**
* Verifies the provided APK's v2 signatures and outputs the results into the provided
* {@code result}. APK is considered verified only if there are no errors reported in the
* {@code result}.
*/
private static void verify(
DataSource beforeApkSigningBlock,
ByteBuffer apkSignatureSchemeV2Block,
DataSource centralDir,
ByteBuffer eocd,
Result result) throws IOException {
Set<ContentDigestAlgorithm> contentDigestsToVerify = new HashSet<>(1);
parseSigners(apkSignatureSchemeV2Block, contentDigestsToVerify, result);
if (result.containsErrors()) {
return;
}
verifyIntegrity(
beforeApkSigningBlock, centralDir, eocd, contentDigestsToVerify, result);
if (!result.containsErrors()) {
result.verified = true;
}
}
/**
* Parses each signer in the provided APK Signature Scheme v2 block and populates
* {@code signerInfos} of the provided {@code result}.
*
* <p>This verifies signatures over {@code signed-data} block contained in each signer block.
* However, this does not verify the integrity of the rest of the APK but rather simply reports
* the expected digests of the rest of the APK (see {@code contentDigestsToVerify}).
*/
private static void parseSigners(
ByteBuffer apkSignatureSchemeV2Block,
Set<ContentDigestAlgorithm> contentDigestsToVerify,
Result result) {
ByteBuffer signers;
try {
signers = getLengthPrefixedSlice(apkSignatureSchemeV2Block);
} catch (IOException e) {
result.addError(Issue.V2_SIG_MALFORMED_SIGNERS);
return;
}
if (!signers.hasRemaining()) {
result.addError(Issue.V2_SIG_NO_SIGNERS);
return;
}
CertificateFactory certFactory;
try {
certFactory = CertificateFactory.getInstance("X.509");
} catch (CertificateException e) {
throw new RuntimeException("Failed to obtain X.509 CertificateFactory", e);
}
int signerCount = 0;
while (signers.hasRemaining()) {
int signerIndex = signerCount;
signerCount++;
Result.SignerInfo signerInfo = new Result.SignerInfo();
signerInfo.index = signerIndex;
result.signers.add(signerInfo);
try {
ByteBuffer signer = getLengthPrefixedSlice(signers);
parseSigner(signer, certFactory, signerInfo, contentDigestsToVerify);
} catch (IOException | BufferUnderflowException e) {
signerInfo.addError(Issue.V2_SIG_MALFORMED_SIGNER);
return;
}
}
}
/**
* Parses the provided signer block and populates the {@code result}.
*
* <p>This verifies signatures over {@code signed-data} contained in this block but does not
* verify the integrity of the rest of the APK. Rather, this method adds to the
* {@code contentDigestsToVerify}.
*/
private static void parseSigner(
ByteBuffer signerBlock,
CertificateFactory certFactory,
Result.SignerInfo result,
Set<ContentDigestAlgorithm> contentDigestsToVerify) throws IOException {
ByteBuffer signedData = getLengthPrefixedSlice(signerBlock);
byte[] signedDataBytes = new byte[signedData.remaining()];
signedData.get(signedDataBytes);
signedData.flip();
result.signedData = signedDataBytes;
ByteBuffer signatures = getLengthPrefixedSlice(signerBlock);
byte[] publicKeyBytes = readLengthPrefixedByteArray(signerBlock);
// Parse the signatures block and identify supported signatures
int signatureCount = 0;
List<SupportedSignature> supportedSignatures = new ArrayList<>(1);
while (signatures.hasRemaining()) {
signatureCount++;
try {
ByteBuffer signature = getLengthPrefixedSlice(signatures);
int sigAlgorithmId = signature.getInt();
byte[] sigBytes = readLengthPrefixedByteArray(signature);
result.signatures.add(
new Result.SignerInfo.Signature(sigAlgorithmId, sigBytes));
SignatureAlgorithm signatureAlgorithm = SignatureAlgorithm.findById(sigAlgorithmId);
if (signatureAlgorithm == null) {
result.addWarning(Issue.V2_SIG_UNKNOWN_SIG_ALGORITHM, sigAlgorithmId);
continue;
}
supportedSignatures.add(new SupportedSignature(signatureAlgorithm, sigBytes));
} catch (IOException | BufferUnderflowException e) {
result.addError(Issue.V2_SIG_MALFORMED_SIGNATURE, signatureCount);
return;
}
}
if (result.signatures.isEmpty()) {
result.addError(Issue.V2_SIG_NO_SIGNATURES);
return;
}
// Verify signatures over signed-data block using the public key
List<SupportedSignature> signaturesToVerify = getSignaturesToVerify(supportedSignatures);
if (signaturesToVerify.isEmpty()) {
result.addError(Issue.V2_SIG_NO_SUPPORTED_SIGNATURES);
return;
}
for (SupportedSignature signature : signaturesToVerify) {
SignatureAlgorithm signatureAlgorithm = signature.algorithm;
String jcaSignatureAlgorithm =
signatureAlgorithm.getJcaSignatureAlgorithmAndParams().getFirst();
AlgorithmParameterSpec jcaSignatureAlgorithmParams =
signatureAlgorithm.getJcaSignatureAlgorithmAndParams().getSecond();
String keyAlgorithm = signatureAlgorithm.getJcaKeyAlgorithm();
PublicKey publicKey;
try {
publicKey =
KeyFactory.getInstance(keyAlgorithm).generatePublic(
new X509EncodedKeySpec(publicKeyBytes));
} catch (Exception e) {
result.addError(Issue.V2_SIG_MALFORMED_PUBLIC_KEY, e);
return;
}
try {
Signature sig = Signature.getInstance(jcaSignatureAlgorithm);
sig.initVerify(publicKey);
if (jcaSignatureAlgorithmParams != null) {
sig.setParameter(jcaSignatureAlgorithmParams);
}
signedData.position(0);
sig.update(signedData);
byte[] sigBytes = signature.signature;
if (!sig.verify(sigBytes)) {
result.addError(Issue.V2_SIG_DID_NOT_VERIFY, signatureAlgorithm);
return;
}
result.verifiedSignatures.put(signatureAlgorithm, sigBytes);
contentDigestsToVerify.add(signatureAlgorithm.getContentDigestAlgorithm());
} catch (Exception e) {
result.addError(Issue.V2_SIG_VERIFY_EXCEPTION, signatureAlgorithm, e);
return;
}
}
// At least one signature over signedData has verified. We can now parse signed-data.
signedData.position(0);
ByteBuffer digests = getLengthPrefixedSlice(signedData);
ByteBuffer certificates = getLengthPrefixedSlice(signedData);
ByteBuffer additionalAttributes = getLengthPrefixedSlice(signedData);
// Parse the certificates block
int certificateIndex = -1;
while (certificates.hasRemaining()) {
certificateIndex++;
byte[] encodedCert = readLengthPrefixedByteArray(certificates);
X509Certificate certificate;
try {
certificate =
(X509Certificate)
certFactory.generateCertificate(
new ByteArrayInputStream(encodedCert));
} catch (CertificateException e) {
result.addError(
Issue.V2_SIG_MALFORMED_CERTIFICATE,
certificateIndex,
certificateIndex + 1,
e);
return;
}
// Wrap the cert so that the result's getEncoded returns exactly the original encoded
// form. Without this, getEncoded may return a different form from what was stored in
// the signature. This is becase some X509Certificate(Factory) implementations re-encode
// certificates.
certificate = new GuaranteedEncodedFormX509Certificate(certificate, encodedCert);
result.certs.add(certificate);
}
if (result.certs.isEmpty()) {
result.addError(Issue.V2_SIG_NO_CERTIFICATES);
return;
}
X509Certificate mainCertificate = result.certs.get(0);
byte[] certificatePublicKeyBytes = mainCertificate.getPublicKey().getEncoded();
if (!Arrays.equals(publicKeyBytes, certificatePublicKeyBytes)) {
result.addError(
Issue.V2_SIG_PUBLIC_KEY_MISMATCH_BETWEEN_CERTIFICATE_AND_SIGNATURES_RECORD,
toHex(certificatePublicKeyBytes),
toHex(publicKeyBytes));
return;
}
// Parse the digests block
int digestCount = 0;
while (digests.hasRemaining()) {
digestCount++;
try {
ByteBuffer digest = getLengthPrefixedSlice(digests);
int sigAlgorithmId = digest.getInt();
byte[] digestBytes = readLengthPrefixedByteArray(digest);
result.contentDigests.add(
new Result.SignerInfo.ContentDigest(sigAlgorithmId, digestBytes));
} catch (IOException | BufferUnderflowException e) {
result.addError(Issue.V2_SIG_MALFORMED_DIGEST, digestCount);
return;
}
}
List<Integer> sigAlgsFromSignaturesRecord = new ArrayList<>(result.signatures.size());
for (Result.SignerInfo.Signature signature : result.signatures) {
sigAlgsFromSignaturesRecord.add(signature.getAlgorithmId());
}
List<Integer> sigAlgsFromDigestsRecord = new ArrayList<>(result.contentDigests.size());
for (Result.SignerInfo.ContentDigest digest : result.contentDigests) {
sigAlgsFromDigestsRecord.add(digest.getSignatureAlgorithmId());
}
if (!sigAlgsFromSignaturesRecord.equals(sigAlgsFromDigestsRecord)) {
result.addError(
Issue.V2_SIG_SIG_ALG_MISMATCH_BETWEEN_SIGNATURES_AND_DIGESTS_RECORDS,
sigAlgsFromSignaturesRecord,
sigAlgsFromDigestsRecord);
return;
}
// Parse the additional attributes block.
int additionalAttributeCount = 0;
while (additionalAttributes.hasRemaining()) {
additionalAttributeCount++;
try {
ByteBuffer attribute = getLengthPrefixedSlice(additionalAttributes);
int id = attribute.getInt();
byte[] value = readLengthPrefixedByteArray(attribute);
result.additionalAttributes.add(
new Result.SignerInfo.AdditionalAttribute(id, value));
result.addWarning(Issue.V2_SIG_UNKNOWN_ADDITIONAL_ATTRIBUTE, id);
} catch (IOException | BufferUnderflowException e) {
result.addError(
Issue.V2_SIG_MALFORMED_ADDITIONAL_ATTRIBUTE, additionalAttributeCount);
return;
}
}
}
private static List<SupportedSignature> getSignaturesToVerify(
List<SupportedSignature> signatures) {
// Pick the signature with the strongest algorithm, to mimic Android's behavior.
SignatureAlgorithm bestSigAlgorithm = null;
byte[] bestSigAlgorithmSignatureBytes = null;
for (SupportedSignature sig : signatures) {
SignatureAlgorithm sigAlgorithm = sig.algorithm;
if ((bestSigAlgorithm == null)
|| (compareSignatureAlgorithm(sigAlgorithm, bestSigAlgorithm) > 0)) {
bestSigAlgorithm = sigAlgorithm;
bestSigAlgorithmSignatureBytes = sig.signature;
}
}
if (bestSigAlgorithm == null) {
return Collections.emptyList();
} else {
return Collections.singletonList(
new SupportedSignature(bestSigAlgorithm, bestSigAlgorithmSignatureBytes));
}
}
private static class SupportedSignature {
private final SignatureAlgorithm algorithm;
private final byte[] signature;
private SupportedSignature(SignatureAlgorithm algorithm, byte[] signature) {
this.algorithm = algorithm;
this.signature = signature;
}
}
/**
* Returns positive number if {@code alg1} is preferred over {@code alg2}, {@code -1} if
* {@code alg2} is preferred over {@code alg1}, and {@code 0} if there is no preference.
*/
private static int compareSignatureAlgorithm(SignatureAlgorithm alg1, SignatureAlgorithm alg2) {
ContentDigestAlgorithm digestAlg1 = alg1.getContentDigestAlgorithm();
ContentDigestAlgorithm digestAlg2 = alg2.getContentDigestAlgorithm();
return compareContentDigestAlgorithm(digestAlg1, digestAlg2);
}
/**
* Returns positive number if {@code alg1} is preferred over {@code alg2}, {@code -1} if
* {@code alg2} is preferred over {@code alg1}, and {@code 0} if there is no preference.
*/
private static int compareContentDigestAlgorithm(
ContentDigestAlgorithm alg1,
ContentDigestAlgorithm alg2) {
switch (alg1) {
case CHUNKED_SHA256:
switch (alg2) {
case CHUNKED_SHA256:
return 0;
case CHUNKED_SHA512:
return -1;
default:
throw new IllegalArgumentException("Unknown alg2: " + alg2);
}
case CHUNKED_SHA512:
switch (alg2) {
case CHUNKED_SHA256:
return 1;
case CHUNKED_SHA512:
return 0;
default:
throw new IllegalArgumentException("Unknown alg2: " + alg2);
}
default:
throw new IllegalArgumentException("Unknown alg1: " + alg1);
}
}
/**
* Verifies integrity of the APK outside of the APK Signing Block by computing digests of the
* APK and comparing them against the digests listed in APK Signing Block. The expected digests
* taken from {@code v2SchemeSignerInfos} of the provided {@code result}.
*/
private static void verifyIntegrity(
DataSource beforeApkSigningBlock,
DataSource centralDir,
ByteBuffer eocd,
Set<ContentDigestAlgorithm> contentDigestAlgorithms,
Result result) throws IOException {
if (contentDigestAlgorithms.isEmpty()) {
// This should never occur because this method is invoked once at least one signature
// is verified, meaning at least one content digest is known.
throw new RuntimeException("No content digests found");
}
// For the purposes of verifying integrity, ZIP End of Central Directory (EoCD) must be
// treated as though its Central Directory offset points to the start of APK Signing Block.
// We thus modify the EoCD accordingly.
ByteBuffer modifiedEocd = ByteBuffer.allocate(eocd.remaining());
modifiedEocd.order(ByteOrder.LITTLE_ENDIAN);
modifiedEocd.put(eocd);
modifiedEocd.flip();
ZipUtils.setZipEocdCentralDirectoryOffset(modifiedEocd, beforeApkSigningBlock.size());
Map<ContentDigestAlgorithm, byte[]> actualContentDigests;
try {
actualContentDigests =
V2SchemeSigner.computeContentDigests(
contentDigestAlgorithms,
new DataSource[] {
beforeApkSigningBlock,
centralDir,
new ByteBufferDataSource(modifiedEocd)
});
} catch (DigestException e) {
throw new RuntimeException("Failed to compute content digests", e);
}
if (!contentDigestAlgorithms.equals(actualContentDigests.keySet())) {
throw new RuntimeException(
"Mismatch between sets of requested and computed content digests"
+ " . Requested: " + contentDigestAlgorithms
+ ", computed: " + actualContentDigests.keySet());
}
// Compare digests computed over the rest of APK against the corresponding expected digests
// in signer blocks.
for (Result.SignerInfo signerInfo : result.signers) {
for (Result.SignerInfo.ContentDigest expected : signerInfo.contentDigests) {
SignatureAlgorithm signatureAlgorithm =
SignatureAlgorithm.findById(expected.getSignatureAlgorithmId());
if (signatureAlgorithm == null) {
continue;
}
ContentDigestAlgorithm contentDigestAlgorithm =
signatureAlgorithm.getContentDigestAlgorithm();
byte[] expectedDigest = expected.getValue();
byte[] actualDigest = actualContentDigests.get(contentDigestAlgorithm);
if (!Arrays.equals(expectedDigest, actualDigest)) {
signerInfo.addError(
Issue.V2_SIG_APK_DIGEST_DID_NOT_VERIFY,
contentDigestAlgorithm,
toHex(expectedDigest),
toHex(actualDigest));
continue;
}
signerInfo.verifiedContentDigests.put(contentDigestAlgorithm, actualDigest);
}
}
}
/**
* APK Signature Scheme v2 block and additional information relevant to verifying the signatures
* contained in the block against the file.
*/
private static class SignatureInfo {
/** Contents of APK Signature Scheme v2 block. */
private final ByteBuffer signatureBlock;
/** Position of the APK Signing Block in the file. */
private final long apkSigningBlockOffset;
/** Position of the ZIP Central Directory in the file. */
private final long centralDirOffset;
/** Position of the ZIP End of Central Directory (EoCD) in the file. */
private final long eocdOffset;
/** Contents of ZIP End of Central Directory (EoCD) of the file. */
private final ByteBuffer eocd;
private SignatureInfo(
ByteBuffer signatureBlock,
long apkSigningBlockOffset,
long centralDirOffset,
long eocdOffset,
ByteBuffer eocd) {
this.signatureBlock = signatureBlock;
this.apkSigningBlockOffset = apkSigningBlockOffset;
this.centralDirOffset = centralDirOffset;
this.eocdOffset = eocdOffset;
this.eocd = eocd;
}
}
/**
* Returns the APK Signature Scheme v2 block contained in the provided APK file and the
* additional information relevant for verifying the block against the file.
*
* @throws SignatureNotFoundException if the APK is not signed using APK Signature Scheme v2
* @throws IOException if an I/O error occurs while reading the APK
*/
private static SignatureInfo findSignature(
DataSource apk, ApkUtils.ZipSections zipSections, Result result)
throws IOException, SignatureNotFoundException {
long centralDirStartOffset = zipSections.getZipCentralDirectoryOffset();
long centralDirEndOffset =
centralDirStartOffset + zipSections.getZipCentralDirectorySizeBytes();
long eocdStartOffset = zipSections.getZipEndOfCentralDirectoryOffset();
if (centralDirEndOffset != eocdStartOffset) {
throw new SignatureNotFoundException(
"ZIP Central Directory is not immediately followed by End of Central Directory"
+ ". CD end: " + centralDirEndOffset
+ ", EoCD start: " + eocdStartOffset);
}
// Find the APK Signing Block. The block immediately precedes the Central Directory.
ByteBuffer eocd = zipSections.getZipEndOfCentralDirectory();
Pair<ByteBuffer, Long> apkSigningBlockAndOffset =
findApkSigningBlock(apk, centralDirStartOffset);
ByteBuffer apkSigningBlock = apkSigningBlockAndOffset.getFirst();
long apkSigningBlockOffset = apkSigningBlockAndOffset.getSecond();
// Find the APK Signature Scheme v2 Block inside the APK Signing Block.
ByteBuffer apkSignatureSchemeV2Block =
findApkSignatureSchemeV2Block(apkSigningBlock, result);
return new SignatureInfo(
apkSignatureSchemeV2Block,
apkSigningBlockOffset,
centralDirStartOffset,
eocdStartOffset,
eocd);
}
private static Pair<ByteBuffer, Long> findApkSigningBlock(
DataSource apk, long centralDirOffset) throws IOException, SignatureNotFoundException {
// FORMAT:
// OFFSET DATA TYPE DESCRIPTION
// * @+0 bytes uint64: size in bytes (excluding this field)
// * @+8 bytes payload
// * @-24 bytes uint64: size in bytes (same as the one above)
// * @-16 bytes uint128: magic
if (centralDirOffset < APK_SIG_BLOCK_MIN_SIZE) {
throw new SignatureNotFoundException(
"APK too small for APK Signing Block. ZIP Central Directory offset: "
+ centralDirOffset);
}
// Read the magic and offset in file from the footer section of the block:
// * uint64: size of block
// * 16 bytes: magic
ByteBuffer footer = apk.getByteBuffer(centralDirOffset - 24, 24);
footer.order(ByteOrder.LITTLE_ENDIAN);
if ((footer.getLong(8) != APK_SIG_BLOCK_MAGIC_LO)
|| (footer.getLong(16) != APK_SIG_BLOCK_MAGIC_HI)) {
throw new SignatureNotFoundException(
"No APK Signing Block before ZIP Central Directory");
}
// Read and compare size fields
long apkSigBlockSizeInFooter = footer.getLong(0);
if ((apkSigBlockSizeInFooter < footer.capacity())
|| (apkSigBlockSizeInFooter > Integer.MAX_VALUE - 8)) {
throw new SignatureNotFoundException(
"APK Signing Block size out of range: " + apkSigBlockSizeInFooter);
}
int totalSize = (int) (apkSigBlockSizeInFooter + 8);
long apkSigBlockOffset = centralDirOffset - totalSize;
if (apkSigBlockOffset < 0) {
throw new SignatureNotFoundException(
"APK Signing Block offset out of range: " + apkSigBlockOffset);
}
ByteBuffer apkSigBlock = apk.getByteBuffer(apkSigBlockOffset, totalSize);
apkSigBlock.order(ByteOrder.LITTLE_ENDIAN);
long apkSigBlockSizeInHeader = apkSigBlock.getLong(0);
if (apkSigBlockSizeInHeader != apkSigBlockSizeInFooter) {
throw new SignatureNotFoundException(
"APK Signing Block sizes in header and footer do not match: "
+ apkSigBlockSizeInHeader + " vs " + apkSigBlockSizeInFooter);
}
return Pair.of(apkSigBlock, apkSigBlockOffset);
}
private static ByteBuffer findApkSignatureSchemeV2Block(
ByteBuffer apkSigningBlock,
Result result) throws SignatureNotFoundException {
checkByteOrderLittleEndian(apkSigningBlock);
// FORMAT:
// OFFSET DATA TYPE DESCRIPTION
// * @+0 bytes uint64: size in bytes (excluding this field)
// * @+8 bytes pairs
// * @-24 bytes uint64: size in bytes (same as the one above)
// * @-16 bytes uint128: magic
ByteBuffer pairs = sliceFromTo(apkSigningBlock, 8, apkSigningBlock.capacity() - 24);
int entryCount = 0;
while (pairs.hasRemaining()) {
entryCount++;
if (pairs.remaining() < 8) {
throw new SignatureNotFoundException(
"Insufficient data to read size of APK Signing Block entry #" + entryCount);
}
long lenLong = pairs.getLong();
if ((lenLong < 4) || (lenLong > Integer.MAX_VALUE)) {
throw new SignatureNotFoundException(
"APK Signing Block entry #" + entryCount
+ " size out of range: " + lenLong);
}
int len = (int) lenLong;
int nextEntryPos = pairs.position() + len;
if (len > pairs.remaining()) {
throw new SignatureNotFoundException(
"APK Signing Block entry #" + entryCount + " size out of range: " + len
+ ", available: " + pairs.remaining());
}
int id = pairs.getInt();
if (id == APK_SIGNATURE_SCHEME_V2_BLOCK_ID) {
return getByteBuffer(pairs, len - 4);
}
result.addWarning(Issue.APK_SIG_BLOCK_UNKNOWN_ENTRY_ID, id);
pairs.position(nextEntryPos);
}
throw new SignatureNotFoundException(
"No APK Signature Scheme v2 block in APK Signing Block");
}
private static void checkByteOrderLittleEndian(ByteBuffer buffer) {
if (buffer.order() != ByteOrder.LITTLE_ENDIAN) {
throw new IllegalArgumentException("ByteBuffer byte order must be little endian");
}
}
public static class SignatureNotFoundException extends Exception {
private static final long serialVersionUID = 1L;
public SignatureNotFoundException(String message) {
super(message);
}
public SignatureNotFoundException(String message, Throwable cause) {
super(message, cause);
}
}
/**
* Returns new byte buffer whose content is a shared subsequence of this buffer's content
* between the specified start (inclusive) and end (exclusive) positions. As opposed to
* {@link ByteBuffer#slice()}, the returned buffer's byte order is the same as the source
* buffer's byte order.
*/
private static ByteBuffer sliceFromTo(ByteBuffer source, int start, int end) {
if (start < 0) {
throw new IllegalArgumentException("start: " + start);
}
if (end < start) {
throw new IllegalArgumentException("end < start: " + end + " < " + start);
}
int capacity = source.capacity();
if (end > source.capacity()) {
throw new IllegalArgumentException("end > capacity: " + end + " > " + capacity);
}
int originalLimit = source.limit();
int originalPosition = source.position();
try {
source.position(0);
source.limit(end);
source.position(start);
ByteBuffer result = source.slice();
result.order(source.order());
return result;
} finally {
source.position(0);
source.limit(originalLimit);
source.position(originalPosition);
}
}
/**
* Relative <em>get</em> method for reading {@code size} number of bytes from the current
* position of this buffer.
*
* <p>This method reads the next {@code size} bytes at this buffer's current position,
* returning them as a {@code ByteBuffer} with start set to 0, limit and capacity set to
* {@code size}, byte order set to this buffer's byte order; and then increments the position by
* {@code size}.
*/
private static ByteBuffer getByteBuffer(ByteBuffer source, int size)
throws BufferUnderflowException {
if (size < 0) {
throw new IllegalArgumentException("size: " + size);
}
int originalLimit = source.limit();
int position = source.position();
int limit = position + size;
if ((limit < position) || (limit > originalLimit)) {
throw new BufferUnderflowException();
}
source.limit(limit);
try {
ByteBuffer result = source.slice();
result.order(source.order());
source.position(limit);
return result;
} finally {
source.limit(originalLimit);
}
}
private static ByteBuffer getLengthPrefixedSlice(ByteBuffer source) throws IOException {
if (source.remaining() < 4) {
throw new IOException(
"Remaining buffer too short to contain length of length-prefixed field."
+ " Remaining: " + source.remaining());
}
int len = source.getInt();
if (len < 0) {
throw new IllegalArgumentException("Negative length");
} else if (len > source.remaining()) {
throw new IOException("Length-prefixed field longer than remaining buffer."
+ " Field length: " + len + ", remaining: " + source.remaining());
}
return getByteBuffer(source, len);
}
private static byte[] readLengthPrefixedByteArray(ByteBuffer buf) throws IOException {
int len = buf.getInt();
if (len < 0) {
throw new IOException("Negative length");
} else if (len > buf.remaining()) {
throw new IOException("Underflow while reading length-prefixed value. Length: " + len
+ ", available: " + buf.remaining());
}
byte[] result = new byte[len];
buf.get(result);
return result;
}
/**
* {@link X509Certificate} whose {@link #getEncoded()} returns the data provided at construction
* time.
*/
private static class GuaranteedEncodedFormX509Certificate extends DelegatingX509Certificate {
private byte[] mEncodedForm;
public GuaranteedEncodedFormX509Certificate(X509Certificate wrapped, byte[] encodedForm) {
super(wrapped);
this.mEncodedForm = (encodedForm != null) ? encodedForm.clone() : null;
}
@Override
public byte[] getEncoded() throws CertificateEncodingException {
return (mEncodedForm != null) ? mEncodedForm.clone() : null;
}
}
private static final char[] HEX_DIGITS = "01234567890abcdef".toCharArray();
private static String toHex(byte[] value) {
StringBuilder sb = new StringBuilder(value.length * 2);
int len = value.length;
for (int i = 0; i < len; i++) {
int hi = (value[i] & 0xff) >>> 4;
int lo = value[i] & 0x0f;
sb.append(HEX_DIGITS[hi]).append(HEX_DIGITS[lo]);
}
return sb.toString();
}
public static class Result {
/** Whether the APK's APK Signature Scheme v2 signature verifies. */
public boolean verified;
public final List<SignerInfo> signers = new ArrayList<>();
private final List<IssueWithParams> mWarnings = new ArrayList<>();
private final List<IssueWithParams> mErrors = new ArrayList<>();
public boolean containsErrors() {
if (!mErrors.isEmpty()) {
return true;
}
if (!signers.isEmpty()) {
for (SignerInfo signer : signers) {
if (signer.containsErrors()) {
return true;
}
}
}
return false;
}
public void addError(Issue msg, Object... parameters) {
mErrors.add(new IssueWithParams(msg, parameters));
}
public void addWarning(Issue msg, Object... parameters) {
mWarnings.add(new IssueWithParams(msg, parameters));
}
public List<IssueWithParams> getErrors() {
return mErrors;
}
public List<IssueWithParams> getWarnings() {
return mWarnings;
}
public static class SignerInfo {
public int index;
public List<X509Certificate> certs = new ArrayList<>();
public List<ContentDigest> contentDigests = new ArrayList<>();
public Map<ContentDigestAlgorithm, byte[]> verifiedContentDigests = new HashMap<>();
public List<Signature> signatures = new ArrayList<>();
public Map<SignatureAlgorithm, byte[]> verifiedSignatures = new HashMap<>();
public List<AdditionalAttribute> additionalAttributes = new ArrayList<>();
public byte[] signedData;
private final List<IssueWithParams> mWarnings = new ArrayList<>();
private final List<IssueWithParams> mErrors = new ArrayList<>();
public void addError(Issue msg, Object... parameters) {
mErrors.add(new IssueWithParams(msg, parameters));
}
public void addWarning(Issue msg, Object... parameters) {
mWarnings.add(new IssueWithParams(msg, parameters));
}
public boolean containsErrors() {
return !mErrors.isEmpty();
}
public List<IssueWithParams> getErrors() {
return mErrors;
}
public List<IssueWithParams> getWarnings() {
return mWarnings;
}
public static class ContentDigest {
private final int mSignatureAlgorithmId;
private final byte[] mValue;
public ContentDigest(int signatureAlgorithmId, byte[] value) {
mSignatureAlgorithmId = signatureAlgorithmId;
mValue = value;
}
public int getSignatureAlgorithmId() {
return mSignatureAlgorithmId;
}
public byte[] getValue() {
return mValue;
}
}
public static class Signature {
private final int mAlgorithmId;
private final byte[] mValue;
public Signature(int algorithmId, byte[] value) {
mAlgorithmId = algorithmId;
mValue = value;
}
public int getAlgorithmId() {
return mAlgorithmId;
}
public byte[] getValue() {
return mValue;
}
}
public static class AdditionalAttribute {
private final int mId;
private final byte[] mValue;
public AdditionalAttribute(int id, byte[] value) {
mId = id;
mValue = value.clone();
}
public int getId() {
return mId;
}
public byte[] getValue() {
return mValue.clone();
}
}
}
}
}

View File

@@ -0,0 +1,179 @@
/*
* Copyright (C) 2016 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.apksigner.core.internal.util;
import java.math.BigInteger;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import java.security.NoSuchProviderException;
import java.security.Principal;
import java.security.PublicKey;
import java.security.SignatureException;
import java.security.cert.CertificateEncodingException;
import java.security.cert.CertificateException;
import java.security.cert.CertificateExpiredException;
import java.security.cert.CertificateNotYetValidException;
import java.security.cert.X509Certificate;
import java.util.Date;
import java.util.Set;
/**
* {@link X509Certificate} which delegates all method invocations to the provided delegate
* {@code X509Certificate}.
*/
public class DelegatingX509Certificate extends X509Certificate {
private final X509Certificate mDelegate;
public DelegatingX509Certificate(X509Certificate delegate) {
this.mDelegate = delegate;
}
@Override
public Set<String> getCriticalExtensionOIDs() {
return mDelegate.getCriticalExtensionOIDs();
}
@Override
public byte[] getExtensionValue(String oid) {
return mDelegate.getExtensionValue(oid);
}
@Override
public Set<String> getNonCriticalExtensionOIDs() {
return mDelegate.getNonCriticalExtensionOIDs();
}
@Override
public boolean hasUnsupportedCriticalExtension() {
return mDelegate.hasUnsupportedCriticalExtension();
}
@Override
public void checkValidity()
throws CertificateExpiredException, CertificateNotYetValidException {
mDelegate.checkValidity();
}
@Override
public void checkValidity(Date date)
throws CertificateExpiredException, CertificateNotYetValidException {
mDelegate.checkValidity(date);
}
@Override
public int getVersion() {
return mDelegate.getVersion();
}
@Override
public BigInteger getSerialNumber() {
return mDelegate.getSerialNumber();
}
@Override
public Principal getIssuerDN() {
return mDelegate.getIssuerDN();
}
@Override
public Principal getSubjectDN() {
return mDelegate.getSubjectDN();
}
@Override
public Date getNotBefore() {
return mDelegate.getNotBefore();
}
@Override
public Date getNotAfter() {
return mDelegate.getNotAfter();
}
@Override
public byte[] getTBSCertificate() throws CertificateEncodingException {
return mDelegate.getTBSCertificate();
}
@Override
public byte[] getSignature() {
return mDelegate.getSignature();
}
@Override
public String getSigAlgName() {
return mDelegate.getSigAlgName();
}
@Override
public String getSigAlgOID() {
return mDelegate.getSigAlgOID();
}
@Override
public byte[] getSigAlgParams() {
return mDelegate.getSigAlgParams();
}
@Override
public boolean[] getIssuerUniqueID() {
return mDelegate.getIssuerUniqueID();
}
@Override
public boolean[] getSubjectUniqueID() {
return mDelegate.getSubjectUniqueID();
}
@Override
public boolean[] getKeyUsage() {
return mDelegate.getKeyUsage();
}
@Override
public int getBasicConstraints() {
return mDelegate.getBasicConstraints();
}
@Override
public byte[] getEncoded() throws CertificateEncodingException {
return mDelegate.getEncoded();
}
@Override
public void verify(PublicKey key) throws CertificateException, NoSuchAlgorithmException,
InvalidKeyException, NoSuchProviderException, SignatureException {
mDelegate.verify(key);
}
@Override
public void verify(PublicKey key, String sigProvider)
throws CertificateException, NoSuchAlgorithmException, InvalidKeyException,
NoSuchProviderException, SignatureException {
mDelegate.verify(key, sigProvider);
}
@Override
public String toString() {
return mDelegate.toString();
}
@Override
public PublicKey getPublicKey() {
return mDelegate.getPublicKey();
}
}

View File

@@ -13,7 +13,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.apksigner.core.internal.apk.v2;
package com.android.apksigner.core.internal.util;
import com.android.apksigner.core.util.DataSink;

View File

@@ -20,7 +20,6 @@ import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import com.android.apksigner.core.internal.util.ByteBufferSink;
import com.android.apksigner.core.internal.util.Pair;
import com.android.apksigner.core.util.DataSource;
@@ -175,11 +174,10 @@ public abstract class ZipUtils {
// Lower maxCommentSize if the file is too small.
maxCommentSize = (int) Math.min(maxCommentSize, fileSize - ZIP_EOCD_REC_MIN_SIZE);
ByteBuffer buf = ByteBuffer.allocate(ZIP_EOCD_REC_MIN_SIZE + maxCommentSize);
int maxEocdSize = ZIP_EOCD_REC_MIN_SIZE + maxCommentSize;
long bufOffsetInFile = fileSize - maxEocdSize;
ByteBuffer buf = zip.getByteBuffer(bufOffsetInFile, maxEocdSize);
buf.order(ByteOrder.LITTLE_ENDIAN);
long bufOffsetInFile = fileSize - buf.capacity();
zip.feed(bufOffsetInFile, buf.remaining(), new ByteBufferSink(buf));
buf.flip();
int eocdOffsetInBuf = findZipEndOfCentralDirectoryRecord(buf);
if (eocdOffsetInBuf == -1) {
// No EoCD record found in the buffer
@@ -252,10 +250,8 @@ public abstract class ZipUtils {
return false;
}
ByteBuffer sig = ByteBuffer.allocate(4);
ByteBuffer sig = zip.getByteBuffer(locatorPosition, 4);
sig.order(ByteOrder.LITTLE_ENDIAN);
zip.feed(locatorPosition, sig.remaining(), new ByteBufferSink(sig));
sig.flip();
return sig.getInt(0) == ZIP64_EOCD_LOCATOR_SIG;
}