| /* |
| * 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.apksig; |
| |
| import static com.android.apksig.apk.ApkUtils.SOURCE_STAMP_CERTIFICATE_HASH_ZIP_ENTRY_NAME; |
| import static com.android.apksig.apk.ApkUtils.computeSha256DigestBytes; |
| import static com.android.apksig.internal.apk.ApkSigningBlockUtils.VERSION_APK_SIGNATURE_SCHEME_V2; |
| import static com.android.apksig.internal.apk.ApkSigningBlockUtils.VERSION_APK_SIGNATURE_SCHEME_V3; |
| import static com.android.apksig.internal.apk.ApkSigningBlockUtils.VERSION_JAR_SIGNATURE_SCHEME; |
| import static com.android.apksig.internal.apk.v1.V1SchemeSigner.MANIFEST_ENTRY_NAME; |
| |
| import com.android.apksig.apk.ApkFormatException; |
| import com.android.apksig.apk.ApkUtils; |
| import com.android.apksig.internal.apk.AndroidBinXmlParser; |
| import com.android.apksig.internal.apk.ApkSigningBlockUtils; |
| import com.android.apksig.internal.apk.ContentDigestAlgorithm; |
| import com.android.apksig.internal.apk.SignatureAlgorithm; |
| import com.android.apksig.internal.apk.stamp.V2SourceStampVerifier; |
| import com.android.apksig.internal.apk.v1.V1SchemeVerifier; |
| import com.android.apksig.internal.apk.v2.V2SchemeVerifier; |
| import com.android.apksig.internal.apk.v3.V3SchemeVerifier; |
| import com.android.apksig.internal.apk.v4.V4SchemeVerifier; |
| import com.android.apksig.internal.util.AndroidSdkVersion; |
| import com.android.apksig.internal.zip.CentralDirectoryRecord; |
| import com.android.apksig.internal.zip.LocalFileRecord; |
| import com.android.apksig.util.DataSource; |
| import com.android.apksig.util.DataSources; |
| import com.android.apksig.util.RunnablesExecutor; |
| import com.android.apksig.zip.ZipFormatException; |
| |
| import java.io.Closeable; |
| import java.io.File; |
| import java.io.IOException; |
| import java.io.RandomAccessFile; |
| import java.nio.ByteBuffer; |
| import java.security.NoSuchAlgorithmException; |
| import java.security.cert.CertificateEncodingException; |
| import java.security.cert.X509Certificate; |
| 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 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 are expected to verify on |
| * Android. |
| * |
| * <p>Use {@link Builder} to obtain instances of this verifier. |
| * |
| * @see <a href="https://source.android.com/security/apksigning/index.html">Application Signing</a> |
| */ |
| public class ApkVerifier { |
| |
| private static final Map<Integer, String> SUPPORTED_APK_SIG_SCHEME_NAMES = |
| loadSupportedApkSigSchemeNames(); |
| |
| private static Map<Integer,String> loadSupportedApkSigSchemeNames() { |
| Map<Integer, String> supportedMap = new HashMap<>(2); |
| supportedMap.put( |
| ApkSigningBlockUtils.VERSION_APK_SIGNATURE_SCHEME_V2, "APK Signature Scheme v2"); |
| supportedMap.put( |
| ApkSigningBlockUtils.VERSION_APK_SIGNATURE_SCHEME_V3, "APK Signature Scheme v3"); |
| return supportedMap; |
| } |
| |
| private final File mApkFile; |
| private final DataSource mApkDataSource; |
| private final File mV4SignatureFile; |
| |
| private final Integer mMinSdkVersion; |
| private final int mMaxSdkVersion; |
| |
| private ApkVerifier( |
| File apkFile, |
| DataSource apkDataSource, |
| File v4SignatureFile, |
| Integer minSdkVersion, |
| int maxSdkVersion) { |
| mApkFile = apkFile; |
| mApkDataSource = apkDataSource; |
| mV4SignatureFile = v4SignatureFile; |
| mMinSdkVersion = minSdkVersion; |
| mMaxSdkVersion = maxSdkVersion; |
| } |
| |
| /** |
| * 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 such |
| * as their signing certificates. |
| * |
| * <p>Verification succeeds iff the APK's signature is expected to verify on all Android |
| * platform versions specified via the {@link Builder}. If the APK's signature is expected to |
| * not verify on any of the specified platform versions, this method returns a result with one |
| * or more errors and whose {@link Result#isVerified()} returns {@code false}, or this method |
| * throws an exception. |
| * |
| * @throws IOException if an I/O error is encountered while reading the APK |
| * @throws ApkFormatException if the APK is malformed |
| * @throws NoSuchAlgorithmException if the APK's signatures cannot be verified because a |
| * required cryptographic algorithm implementation is missing |
| * @throws IllegalStateException if this verifier's configuration is missing required |
| * information. |
| */ |
| public Result verify() throws IOException, ApkFormatException, NoSuchAlgorithmException, |
| IllegalStateException { |
| Closeable in = null; |
| try { |
| DataSource apk; |
| if (mApkDataSource != null) { |
| apk = mApkDataSource; |
| } else if (mApkFile != null) { |
| RandomAccessFile f = new RandomAccessFile(mApkFile, "r"); |
| in = f; |
| apk = DataSources.asDataSource(f, 0, f.length()); |
| } else { |
| throw new IllegalStateException("APK not provided"); |
| } |
| return verify(apk); |
| } finally { |
| if (in != null) { |
| in.close(); |
| } |
| } |
| } |
| |
| /** |
| * 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 |
| * |
| * @throws IOException if an I/O error is encountered while reading the APK |
| * @throws ApkFormatException if the APK is malformed |
| * @throws NoSuchAlgorithmException if the APK's signatures cannot be verified because a |
| * required cryptographic algorithm implementation is missing |
| */ |
| private Result verify(DataSource apk) |
| throws IOException, ApkFormatException, NoSuchAlgorithmException { |
| if (mMinSdkVersion != null) { |
| if (mMinSdkVersion < 0) { |
| throw new IllegalArgumentException( |
| "minSdkVersion must not be negative: " + mMinSdkVersion); |
| } |
| if ((mMinSdkVersion != null) && (mMinSdkVersion > mMaxSdkVersion)) { |
| throw new IllegalArgumentException( |
| "minSdkVersion (" + mMinSdkVersion + ") > maxSdkVersion (" + mMaxSdkVersion |
| + ")"); |
| } |
| } |
| int maxSdkVersion = mMaxSdkVersion; |
| |
| ApkUtils.ZipSections zipSections; |
| try { |
| zipSections = ApkUtils.findZipSections(apk); |
| } catch (ZipFormatException e) { |
| throw new ApkFormatException("Malformed APK: not a ZIP archive", e); |
| } |
| |
| ByteBuffer androidManifest = null; |
| |
| int minSdkVersion; |
| if (mMinSdkVersion != null) { |
| // No need to obtain minSdkVersion from the APK's AndroidManifest.xml |
| minSdkVersion = mMinSdkVersion; |
| } else { |
| // Need to obtain minSdkVersion from the APK's AndroidManifest.xml |
| if (androidManifest == null) { |
| androidManifest = getAndroidManifestFromApk(apk, zipSections); |
| } |
| minSdkVersion = |
| ApkUtils.getMinSdkVersionFromBinaryAndroidManifest(androidManifest.slice()); |
| if (minSdkVersion > mMaxSdkVersion) { |
| throw new IllegalArgumentException( |
| "minSdkVersion from APK (" + minSdkVersion + ") > maxSdkVersion (" |
| + mMaxSdkVersion + ")"); |
| } |
| } |
| |
| Result result = new Result(); |
| Map<Integer, Map<ContentDigestAlgorithm, byte[]>> signatureSchemeApkContentDigests = |
| new HashMap<>(); |
| |
| // The SUPPORTED_APK_SIG_SCHEME_NAMES contains the mapping from version number to scheme |
| // name, but the verifiers use this parameter as the schemes supported by the target SDK |
| // range. Since the code below skips signature verification based on max SDK the mapping of |
| // supported schemes needs to be modified to ensure the verifiers do not report a stripped |
| // signature for an SDK range that does not support that signature version. For instance an |
| // APK with V1, V2, and V3 signatures and a max SDK of O would skip the V3 signature |
| // verification, but the SUPPORTED_APK_SIG_SCHEME_NAMES contains version 3, so when the V2 |
| // verification is performed it would see the stripping protection attribute, see that V3 |
| // is in the list of supported signatures, and report a stripped signature. |
| Map<Integer, String> supportedSchemeNames; |
| if (maxSdkVersion >= AndroidSdkVersion.P) { |
| supportedSchemeNames = SUPPORTED_APK_SIG_SCHEME_NAMES; |
| } else if (maxSdkVersion >= AndroidSdkVersion.N) { |
| supportedSchemeNames = new HashMap<>(1); |
| supportedSchemeNames.put(ApkSigningBlockUtils.VERSION_APK_SIGNATURE_SCHEME_V2, |
| SUPPORTED_APK_SIG_SCHEME_NAMES.get( |
| ApkSigningBlockUtils.VERSION_APK_SIGNATURE_SCHEME_V2)); |
| } else { |
| supportedSchemeNames = Collections.emptyMap(); |
| } |
| // Android N and newer attempts to verify APKs using the APK Signing Block, which can |
| // include v2 and/or v3 signatures. If none is found, it falls back to JAR signature |
| // verification. If the signature is found but does not verify, the APK is rejected. |
| Set<Integer> foundApkSigSchemeIds = new HashSet<>(2); |
| if (maxSdkVersion >= AndroidSdkVersion.N) { |
| RunnablesExecutor executor = RunnablesExecutor.SINGLE_THREADED; |
| // Android P and newer attempts to verify APKs using APK Signature Scheme v3 |
| if (maxSdkVersion >= AndroidSdkVersion.P) { |
| try { |
| ApkSigningBlockUtils.Result v3Result = |
| V3SchemeVerifier.verify( |
| executor, |
| apk, |
| zipSections, |
| Math.max(minSdkVersion, AndroidSdkVersion.P), |
| maxSdkVersion); |
| foundApkSigSchemeIds.add(ApkSigningBlockUtils.VERSION_APK_SIGNATURE_SCHEME_V3); |
| result.mergeFrom(v3Result); |
| signatureSchemeApkContentDigests.put( |
| ApkSigningBlockUtils.VERSION_APK_SIGNATURE_SCHEME_V3, |
| getApkContentDigestsFromSigningSchemeResult(v3Result)); |
| } catch (ApkSigningBlockUtils.SignatureNotFoundException ignored) { |
| // v3 signature not required |
| } |
| if (result.containsErrors()) { |
| return result; |
| } |
| } |
| |
| // Attempt to verify the APK using v2 signing if necessary. Platforms prior to Android P |
| // ignore APK Signature Scheme v3 signatures and always attempt to verify either JAR or |
| // APK Signature Scheme v2 signatures. Android P onwards verifies v2 signatures only if |
| // no APK Signature Scheme v3 (or newer scheme) signatures were found. |
| if (minSdkVersion < AndroidSdkVersion.P || foundApkSigSchemeIds.isEmpty()) { |
| try { |
| ApkSigningBlockUtils.Result v2Result = |
| V2SchemeVerifier.verify( |
| executor, |
| apk, |
| zipSections, |
| supportedSchemeNames, |
| foundApkSigSchemeIds, |
| Math.max(minSdkVersion, AndroidSdkVersion.N), |
| maxSdkVersion); |
| foundApkSigSchemeIds.add(ApkSigningBlockUtils.VERSION_APK_SIGNATURE_SCHEME_V2); |
| result.mergeFrom(v2Result); |
| signatureSchemeApkContentDigests.put( |
| ApkSigningBlockUtils.VERSION_APK_SIGNATURE_SCHEME_V2, |
| getApkContentDigestsFromSigningSchemeResult(v2Result)); |
| } catch (ApkSigningBlockUtils.SignatureNotFoundException ignored) { |
| // v2 signature not required |
| } |
| if (result.containsErrors()) { |
| return result; |
| } |
| } |
| |
| // If v4 file is specified, use additional verification on it |
| if (mV4SignatureFile != null) { |
| final ApkSigningBlockUtils.Result v4Result = |
| V4SchemeVerifier.verify(apk, mV4SignatureFile); |
| foundApkSigSchemeIds.add( |
| ApkSigningBlockUtils.VERSION_APK_SIGNATURE_SCHEME_V4); |
| result.mergeFrom(v4Result); |
| if (result.containsErrors()) { |
| return result; |
| } |
| } |
| } |
| |
| // Android O and newer requires that APKs targeting security sandbox version 2 and higher |
| // are signed using APK Signature Scheme v2 or newer. |
| if (maxSdkVersion >= AndroidSdkVersion.O) { |
| if (androidManifest == null) { |
| androidManifest = getAndroidManifestFromApk(apk, zipSections); |
| } |
| int targetSandboxVersion = |
| getTargetSandboxVersionFromBinaryAndroidManifest(androidManifest.slice()); |
| if (targetSandboxVersion > 1) { |
| if (foundApkSigSchemeIds.isEmpty()) { |
| result.addError( |
| Issue.NO_SIG_FOR_TARGET_SANDBOX_VERSION, |
| targetSandboxVersion); |
| } |
| } |
| } |
| |
| List<CentralDirectoryRecord> cdRecords = |
| V1SchemeVerifier.parseZipCentralDirectory(apk, zipSections); |
| |
| // Attempt to verify the APK using JAR signing if necessary. Platforms prior to Android N |
| // ignore APK Signature Scheme v2 signatures and always attempt to verify JAR signatures. |
| // Android N onwards verifies JAR signatures only if no APK Signature Scheme v2 (or newer |
| // scheme) signatures were found. |
| if ((minSdkVersion < AndroidSdkVersion.N) || (foundApkSigSchemeIds.isEmpty())) { |
| V1SchemeVerifier.Result v1Result = |
| V1SchemeVerifier.verify( |
| apk, |
| zipSections, |
| supportedSchemeNames, |
| foundApkSigSchemeIds, |
| minSdkVersion, |
| maxSdkVersion); |
| result.mergeFrom(v1Result); |
| signatureSchemeApkContentDigests.put( |
| ApkSigningBlockUtils.VERSION_JAR_SIGNATURE_SCHEME, |
| getApkContentDigestFromV1SigningScheme(cdRecords, apk, zipSections)); |
| } |
| if (result.containsErrors()) { |
| return result; |
| } |
| |
| // Verify the SourceStamp, if found in the APK. |
| try { |
| CentralDirectoryRecord sourceStampCdRecord = null; |
| for (CentralDirectoryRecord cdRecord : cdRecords) { |
| if (SOURCE_STAMP_CERTIFICATE_HASH_ZIP_ENTRY_NAME.equals( |
| cdRecord.getName())) { |
| sourceStampCdRecord = cdRecord; |
| break; |
| } |
| } |
| // If SourceStamp file is found inside the APK, there must be a SourceStamp |
| // block in the APK signing block as well. |
| if (sourceStampCdRecord != null) { |
| byte[] sourceStampCertificateDigest = |
| LocalFileRecord.getUncompressedData( |
| apk, |
| sourceStampCdRecord, |
| zipSections.getZipCentralDirectoryOffset()); |
| ApkSigningBlockUtils.Result sourceStampResult = |
| V2SourceStampVerifier.verify( |
| apk, |
| zipSections, |
| sourceStampCertificateDigest, |
| signatureSchemeApkContentDigests, |
| Math.max(minSdkVersion, AndroidSdkVersion.R), |
| maxSdkVersion); |
| result.mergeFrom(sourceStampResult); |
| } |
| } catch (ApkSigningBlockUtils.SignatureNotFoundException ignored) { |
| result.addWarning(Issue.SOURCE_STAMP_SIG_MISSING); |
| } catch (ZipFormatException e) { |
| throw new ApkFormatException("Failed to read APK", e); |
| } |
| if (result.containsErrors()) { |
| return result; |
| } |
| |
| // Check whether v1 and v2 scheme signer identifies match, provided both v1 and v2 |
| // signatures verified. |
| if ((result.isVerifiedUsingV1Scheme()) && (result.isVerifiedUsingV2Scheme())) { |
| ArrayList<Result.V1SchemeSignerInfo> v1Signers = |
| new ArrayList<>(result.getV1SchemeSigners()); |
| ArrayList<Result.V2SchemeSignerInfo> v2Signers = |
| new ArrayList<>(result.getV2SchemeSigners()); |
| ArrayList<ByteArray> v1SignerCerts = new ArrayList<>(); |
| ArrayList<ByteArray> v2SignerCerts = new ArrayList<>(); |
| for (Result.V1SchemeSignerInfo signer : v1Signers) { |
| try { |
| v1SignerCerts.add(new ByteArray(signer.getCertificate().getEncoded())); |
| } catch (CertificateEncodingException e) { |
| throw new IllegalStateException( |
| "Failed to encode JAR signer " + signer.getName() + " certs", e); |
| } |
| } |
| for (Result.V2SchemeSignerInfo signer : v2Signers) { |
| try { |
| v2SignerCerts.add(new ByteArray(signer.getCertificate().getEncoded())); |
| } catch (CertificateEncodingException e) { |
| throw new IllegalStateException( |
| "Failed to encode APK Signature Scheme v2 signer (index: " |
| + signer.getIndex() + ") certs", |
| e); |
| } |
| } |
| |
| for (int i = 0; i < v1SignerCerts.size(); i++) { |
| ByteArray v1Cert = v1SignerCerts.get(i); |
| if (!v2SignerCerts.contains(v1Cert)) { |
| Result.V1SchemeSignerInfo v1Signer = v1Signers.get(i); |
| v1Signer.addError(Issue.V2_SIG_MISSING); |
| break; |
| } |
| } |
| for (int i = 0; i < v2SignerCerts.size(); i++) { |
| ByteArray v2Cert = v2SignerCerts.get(i); |
| if (!v1SignerCerts.contains(v2Cert)) { |
| Result.V2SchemeSignerInfo v2Signer = v2Signers.get(i); |
| v2Signer.addError(Issue.JAR_SIG_MISSING); |
| break; |
| } |
| } |
| } |
| |
| // If there is a v3 scheme signer and an earlier scheme signer, make sure that there is a |
| // match, or in the event of signing certificate rotation, that the v1/v2 scheme signer |
| // matches the oldest signing certificate in the provided SigningCertificateLineage |
| if (result.isVerifiedUsingV3Scheme() |
| && (result.isVerifiedUsingV1Scheme() || result.isVerifiedUsingV2Scheme())) { |
| SigningCertificateLineage lineage = result.getSigningCertificateLineage(); |
| X509Certificate oldSignerCert; |
| if (result.isVerifiedUsingV1Scheme()) { |
| List<Result.V1SchemeSignerInfo> v1Signers = result.getV1SchemeSigners(); |
| if (v1Signers.size() != 1) { |
| // APK Signature Scheme v3 only supports single-signers, error to sign with |
| // multiple and then only one |
| result.addError(Issue.V3_SIG_MULTIPLE_PAST_SIGNERS); |
| } |
| oldSignerCert = v1Signers.get(0).mCertChain.get(0); |
| } else { |
| List<Result.V2SchemeSignerInfo> v2Signers = result.getV2SchemeSigners(); |
| if (v2Signers.size() != 1) { |
| // APK Signature Scheme v3 only supports single-signers, error to sign with |
| // multiple and then only one |
| result.addError(Issue.V3_SIG_MULTIPLE_PAST_SIGNERS); |
| } |
| oldSignerCert = v2Signers.get(0).mCerts.get(0); |
| } |
| if (lineage == null) { |
| // no signing certificate history with which to contend, just make sure that v3 |
| // matches previous versions |
| List<Result.V3SchemeSignerInfo> v3Signers = result.getV3SchemeSigners(); |
| if (v3Signers.size() != 1) { |
| // multiple v3 signers should never exist without rotation history, since |
| // multiple signers implies a different signer for different platform versions |
| result.addError(Issue.V3_SIG_MULTIPLE_SIGNERS); |
| } |
| try { |
| if (!Arrays.equals(oldSignerCert.getEncoded(), |
| v3Signers.get(0).mCerts.get(0).getEncoded())) { |
| result.addError(Issue.V3_SIG_PAST_SIGNERS_MISMATCH); |
| } |
| } catch (CertificateEncodingException e) { |
| // we just go the encoding for the v1/v2 certs above, so must be v3 |
| throw new RuntimeException( |
| "Failed to encode APK Signature Scheme v3 signer cert", e); |
| } |
| } else { |
| // we have some signing history, make sure that the root of the history is the same |
| // as our v1/v2 signer |
| try { |
| lineage = lineage.getSubLineage(oldSignerCert); |
| if (lineage.size() != 1) { |
| // the v1/v2 signer was found, but not at the root of the lineage |
| result.addError(Issue.V3_SIG_PAST_SIGNERS_MISMATCH); |
| } |
| } catch (IllegalArgumentException e) { |
| // the v1/v2 signer was not found in the lineage |
| result.addError(Issue.V3_SIG_PAST_SIGNERS_MISMATCH); |
| } |
| } |
| } |
| |
| |
| // If there is a v4 scheme signer, make sure that their certificates match. |
| // The apkDigest field in the v4 signature should match the selected v2/v3. |
| if (result.isVerifiedUsingV4Scheme()) { |
| List<Result.V4SchemeSignerInfo> v4Signers = result.getV4SchemeSigners(); |
| if (v4Signers.size() != 1) { |
| result.addError(Issue.V4_SIG_MULTIPLE_SIGNERS); |
| } |
| |
| List<ApkSigningBlockUtils.Result.SignerInfo.ContentDigest> digestsFromV4 = |
| v4Signers.get(0).getContentDigests(); |
| if (digestsFromV4.size() != 1) { |
| result.addError(Issue.V4_SIG_V2_V3_DIGESTS_MISMATCH); |
| } |
| final byte[] digestFromV4 = digestsFromV4.get(0).getValue(); |
| |
| if (result.isVerifiedUsingV3Scheme()) { |
| List<Result.V3SchemeSignerInfo> v3Signers = result.getV3SchemeSigners(); |
| if (v3Signers.size() != 1) { |
| result.addError(Issue.V4_SIG_MULTIPLE_SIGNERS); |
| } |
| |
| // Compare certificates. |
| checkV4Certificate(v4Signers.get(0).mCerts, v3Signers.get(0).mCerts, result); |
| |
| // Compare digests. |
| final byte[] digestFromV3 = pickBestDigestForV4( |
| v3Signers.get(0).getContentDigests()); |
| if (!Arrays.equals(digestFromV4, digestFromV3)) { |
| result.addError(Issue.V4_SIG_V2_V3_DIGESTS_MISMATCH); |
| } |
| } else if (result.isVerifiedUsingV2Scheme()) { |
| List<Result.V2SchemeSignerInfo> v2Signers = result.getV2SchemeSigners(); |
| if (v2Signers.size() != 1) { |
| result.addError(Issue.V4_SIG_MULTIPLE_SIGNERS); |
| } |
| |
| // Compare certificates. |
| checkV4Certificate(v4Signers.get(0).mCerts, v2Signers.get(0).mCerts, result); |
| |
| // Compare digests. |
| final byte[] digestFromV2 = pickBestDigestForV4( |
| v2Signers.get(0).getContentDigests()); |
| if (!Arrays.equals(digestFromV4, digestFromV2)) { |
| result.addError(Issue.V4_SIG_V2_V3_DIGESTS_MISMATCH); |
| } |
| } else { |
| throw new RuntimeException("V4 signature must be also verified with V2/V3"); |
| } |
| } |
| |
| // If the targetSdkVersion has a minimum required signature scheme version then verify |
| // that the APK was signed with at least that version. |
| if (androidManifest == null) { |
| androidManifest = getAndroidManifestFromApk(apk, zipSections); |
| } |
| int targetSdkVersion = getTargetSdkVersionFromBinaryAndroidManifest( |
| androidManifest.slice()); |
| int minSchemeVersion = getMinimumSignatureSchemeVersionForTargetSdk(targetSdkVersion); |
| // The platform currently only enforces a single minimum signature scheme version, but when |
| // later platform versions support another minimum version this will need to be expanded to |
| // verify the minimum based on the target and maximum SDK version. |
| if (minSchemeVersion > VERSION_JAR_SIGNATURE_SCHEME && maxSdkVersion >= targetSdkVersion) { |
| switch(minSchemeVersion) { |
| case VERSION_APK_SIGNATURE_SCHEME_V2: |
| if (result.isVerifiedUsingV2Scheme()) { |
| break; |
| } |
| // Allow this case to fall through to the next as a signature satisfying a later |
| // scheme version will also satisfy this requirement. |
| case VERSION_APK_SIGNATURE_SCHEME_V3: |
| if (result.isVerifiedUsingV3Scheme()) { |
| break; |
| } |
| result.addError(Issue.MIN_SIG_SCHEME_FOR_TARGET_SDK_NOT_MET, targetSdkVersion, |
| minSchemeVersion); |
| } |
| } |
| |
| if (result.containsErrors()) { |
| return result; |
| } |
| |
| // Verified |
| result.setVerified(); |
| if (result.isVerifiedUsingV3Scheme()) { |
| List<Result.V3SchemeSignerInfo> v3Signers = result.getV3SchemeSigners(); |
| result.addSignerCertificate(v3Signers.get(v3Signers.size() - 1).getCertificate()); |
| } else if (result.isVerifiedUsingV2Scheme()) { |
| for (Result.V2SchemeSignerInfo signerInfo : result.getV2SchemeSigners()) { |
| result.addSignerCertificate(signerInfo.getCertificate()); |
| } |
| } else if (result.isVerifiedUsingV1Scheme()) { |
| for (Result.V1SchemeSignerInfo signerInfo : result.getV1SchemeSigners()) { |
| result.addSignerCertificate(signerInfo.getCertificate()); |
| } |
| } else { |
| throw new RuntimeException( |
| "APK verified, but has not verified using any of v1, v2 or v3 schemes"); |
| } |
| |
| return result; |
| } |
| |
| private static void checkV4Certificate(List<X509Certificate> v4Certs, List<X509Certificate> v2v3Certs, Result result) { |
| try { |
| byte[] v4Cert = v4Certs.get(0).getEncoded(); |
| byte[] cert = v2v3Certs.get(0).getEncoded(); |
| if (!Arrays.equals(cert, v4Cert)) { |
| result.addError(Issue.V4_SIG_V2_V3_SIGNERS_MISMATCH); |
| } |
| } catch (CertificateEncodingException e) { |
| throw new RuntimeException("Failed to encode APK signer cert", e); |
| } |
| } |
| |
| private static byte[] pickBestDigestForV4(List<ApkSigningBlockUtils.Result.SignerInfo.ContentDigest> contentDigests) { |
| Map<ContentDigestAlgorithm, byte[]> apkContentDigests = new HashMap<>(); |
| collectApkContentDigests(contentDigests, apkContentDigests); |
| return ApkSigningBlockUtils.pickBestDigestForV4(apkContentDigests); |
| } |
| |
| private static Map<ContentDigestAlgorithm, byte[]> getApkContentDigestsFromSigningSchemeResult( |
| ApkSigningBlockUtils.Result apkSigningSchemeResult) { |
| Map<ContentDigestAlgorithm, byte[]> apkContentDigests = new HashMap<>(); |
| for (ApkSigningBlockUtils.Result.SignerInfo signerInfo : apkSigningSchemeResult.signers) { |
| collectApkContentDigests(signerInfo.contentDigests, apkContentDigests); |
| } |
| return apkContentDigests; |
| } |
| |
| private static Map<ContentDigestAlgorithm, byte[]> getApkContentDigestFromV1SigningScheme( |
| List<CentralDirectoryRecord> cdRecords, |
| DataSource apk, |
| ApkUtils.ZipSections zipSections) |
| throws IOException, ApkFormatException { |
| CentralDirectoryRecord manifestCdRecord = null; |
| Map<ContentDigestAlgorithm, byte[]> v1ContentDigest = new HashMap<>(); |
| for (CentralDirectoryRecord cdRecord : cdRecords) { |
| if (MANIFEST_ENTRY_NAME.equals(cdRecord.getName())) { |
| manifestCdRecord = cdRecord; |
| break; |
| } |
| } |
| if (manifestCdRecord == null) { |
| // No JAR signing manifest file found. For SourceStamp verification, returning an empty |
| // digest is enough since this would affect the final digest signed by the stamp, and |
| // thus an empty digest will invalidate that signature. |
| return v1ContentDigest; |
| } |
| try { |
| byte[] manifestBytes = |
| LocalFileRecord.getUncompressedData( |
| apk, manifestCdRecord, zipSections.getZipCentralDirectoryOffset()); |
| v1ContentDigest.put( |
| ContentDigestAlgorithm.SHA256, computeSha256DigestBytes(manifestBytes)); |
| return v1ContentDigest; |
| } catch (ZipFormatException e) { |
| throw new ApkFormatException("Failed to read APK", e); |
| } |
| } |
| |
| private static void collectApkContentDigests(List<ApkSigningBlockUtils.Result.SignerInfo.ContentDigest> contentDigests, Map<ContentDigestAlgorithm, byte[]> apkContentDigests) { |
| for (ApkSigningBlockUtils.Result.SignerInfo.ContentDigest contentDigest : contentDigests) { |
| SignatureAlgorithm signatureAlgorithm = |
| SignatureAlgorithm.findById(contentDigest.getSignatureAlgorithmId()); |
| if (signatureAlgorithm == null) { |
| continue; |
| } |
| ContentDigestAlgorithm contentDigestAlgorithm = |
| signatureAlgorithm.getContentDigestAlgorithm(); |
| apkContentDigests.put(contentDigestAlgorithm, contentDigest.getValue()); |
| } |
| |
| } |
| |
| private static ByteBuffer getAndroidManifestFromApk( |
| DataSource apk, ApkUtils.ZipSections zipSections) |
| throws IOException, ApkFormatException { |
| List<CentralDirectoryRecord> cdRecords = |
| V1SchemeVerifier.parseZipCentralDirectory(apk, zipSections); |
| try { |
| return ApkSigner.getAndroidManifestFromApk( |
| cdRecords, |
| apk.slice(0, zipSections.getZipCentralDirectoryOffset())); |
| } catch (ZipFormatException e) { |
| throw new ApkFormatException("Failed to read AndroidManifest.xml", e); |
| } |
| } |
| |
| /** |
| * Android resource ID of the {@code android:targetSandboxVersion} attribute in |
| * AndroidManifest.xml. |
| */ |
| private static final int TARGET_SANDBOX_VERSION_ATTR_ID = 0x0101054c; |
| private static final String TARGET_SANDBOX_VERSION_ELEMENT_NAME = "manifest"; |
| |
| /** |
| * Android resource ID of the {@code android:targetSdkVersion} attribute in |
| * AndroidManifest.xml. |
| */ |
| private static final int MIN_SDK_VERSION_ATTR_ID = 0x0101020c; |
| private static final int TARGET_SDK_VERSION_ATTR_ID = 0x01010270; |
| private static final String USES_SDK_ELEMENT_NAME = "uses-sdk"; |
| |
| /** |
| * Returns the security sandbox version targeted by an APK with the provided |
| * {@code AndroidManifest.xml}. |
| * |
| * @param androidManifestContents contents of {@code AndroidManifest.xml} in binary Android |
| * resource format |
| * |
| * @throws ApkFormatException if an error occurred while determining the version |
| */ |
| private static int getTargetSandboxVersionFromBinaryAndroidManifest( |
| ByteBuffer androidManifestContents) throws ApkFormatException { |
| return getAttributeValueFromBinaryAndroidManifest(androidManifestContents, |
| TARGET_SANDBOX_VERSION_ELEMENT_NAME, TARGET_SANDBOX_VERSION_ATTR_ID); |
| } |
| |
| /** |
| * Returns the SDK version targeted by an APK with the provided {@code AndroidManifest.xml}. |
| * |
| * @param androidManifestContents contents of {@code AndroidManifest.xml} in binary Android |
| * resource format |
| * @throws ApkFormatException if an error occurred while determining the version |
| */ |
| private static int getTargetSdkVersionFromBinaryAndroidManifest( |
| ByteBuffer androidManifestContents) { |
| // If the targetSdkVersion is not specified then the platform will use the value of the |
| // minSdkVersion; if neither is specified then the platform will use a value of 1. |
| int minSdkVersion = 1; |
| try { |
| return getAttributeValueFromBinaryAndroidManifest(androidManifestContents, |
| USES_SDK_ELEMENT_NAME, TARGET_SDK_VERSION_ATTR_ID); |
| } catch (ApkFormatException e) { |
| // Expected if the APK does not contain a targetSdkVersion attribute or the uses-sdk |
| // element is not specified at all. |
| } |
| androidManifestContents.rewind(); |
| try { |
| minSdkVersion = getAttributeValueFromBinaryAndroidManifest(androidManifestContents, |
| USES_SDK_ELEMENT_NAME, MIN_SDK_VERSION_ATTR_ID); |
| } catch (ApkFormatException e) { |
| // Similar to above, expected if the APK does not contain a minSdkVersion attribute or |
| // the uses-sdk element is not specified at all. |
| } |
| return minSdkVersion; |
| } |
| |
| /** |
| * Returns the integer value of the requested {@code attributeId} in the specified {@code |
| * elementName} from the provided {@code androidManifestContents} in binary Android resource |
| * format. |
| * |
| * @throws ApkFormatException if an error occurred while attempting to obtain the attribute |
| */ |
| private static int getAttributeValueFromBinaryAndroidManifest( |
| ByteBuffer androidManifestContents, String elementName, int attributeId) |
| throws ApkFormatException { |
| // Return the value of the requested attribute from the specified element. |
| try { |
| AndroidBinXmlParser parser = new AndroidBinXmlParser(androidManifestContents); |
| int eventType = parser.getEventType(); |
| while (eventType != AndroidBinXmlParser.EVENT_END_DOCUMENT) { |
| if ((eventType == AndroidBinXmlParser.EVENT_START_ELEMENT) |
| && (elementName.equals(parser.getName())) |
| && (parser.getNamespace().isEmpty())) { |
| int result = 1; |
| for (int i = 0; i < parser.getAttributeCount(); i++) { |
| if (parser.getAttributeNameResourceId(i) == attributeId) { |
| int valueType = parser.getAttributeValueType(i); |
| switch (valueType) { |
| case AndroidBinXmlParser.VALUE_TYPE_INT: |
| result = parser.getAttributeIntValue(i); |
| break; |
| default: |
| throw new ApkFormatException( |
| "Failed to determine APK's " |
| + elementName + " attribute" |
| + ": unsupported value type of" |
| + " AndroidManifest.xml " |
| + String.format("0x%08X", attributeId) |
| + ". Only integer values supported."); |
| } |
| break; |
| } |
| } |
| return result; |
| } |
| eventType = parser.next(); |
| } |
| throw new ApkFormatException( |
| "Failed to determine APK's " + elementName + " attribute " |
| + String.format("0x%08X", attributeId) |
| + " : no " + elementName + " element in AndroidManifest.xml"); |
| } catch (AndroidBinXmlParser.XmlParserException e) { |
| throw new ApkFormatException( |
| "Failed to determine APK's " + elementName + " attribute " |
| + String.format("0x%08X", attributeId) |
| + ": malformed AndroidManifest.xml", e); |
| } |
| } |
| |
| private static int getMinimumSignatureSchemeVersionForTargetSdk(int targetSdkVersion) { |
| if (targetSdkVersion >= AndroidSdkVersion.R) { |
| return VERSION_APK_SIGNATURE_SCHEME_V2; |
| } |
| return VERSION_JAR_SIGNATURE_SCHEME; |
| } |
| |
| /** |
| * 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<V1SchemeSignerInfo> mV1SchemeSigners = new ArrayList<>(); |
| private final List<V1SchemeSignerInfo> mV1SchemeIgnoredSigners = new ArrayList<>(); |
| private final List<V2SchemeSignerInfo> mV2SchemeSigners = new ArrayList<>(); |
| private final List<V3SchemeSignerInfo> mV3SchemeSigners = new ArrayList<>(); |
| private final List<V4SchemeSignerInfo> mV4SchemeSigners = new ArrayList<>(); |
| private SourceStampInfo mSourceStampInfo; |
| |
| private boolean mVerified; |
| private boolean mVerifiedUsingV1Scheme; |
| private boolean mVerifiedUsingV2Scheme; |
| private boolean mVerifiedUsingV3Scheme; |
| private boolean mVerifiedUsingV4Scheme; |
| private boolean mSourceStampVerified; |
| private SigningCertificateLineage mSigningCertificateLineage; |
| |
| /** |
| * 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 JAR signatures verified. |
| */ |
| public boolean isVerifiedUsingV1Scheme() { |
| return mVerifiedUsingV1Scheme; |
| } |
| |
| /** |
| * Returns {@code true} if the APK's APK Signature Scheme v2 signatures verified. |
| */ |
| public boolean isVerifiedUsingV2Scheme() { |
| return mVerifiedUsingV2Scheme; |
| } |
| |
| /** |
| * Returns {@code true} if the APK's APK Signature Scheme v3 signature verified. |
| */ |
| public boolean isVerifiedUsingV3Scheme() { |
| return mVerifiedUsingV3Scheme; |
| } |
| |
| /** |
| * Returns {@code true} if the APK's APK Signature Scheme v4 signature verified. |
| */ |
| public boolean isVerifiedUsingV4Scheme() { |
| return mVerifiedUsingV4Scheme; |
| } |
| |
| /** |
| * Returns {@code true} if the APK's SourceStamp signature verified. |
| */ |
| public boolean isSourceStampVerified() { |
| return mSourceStampVerified; |
| } |
| |
| /** |
| * 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 JAR signers associated with the APK's signature. These are the |
| * signers used by Android. |
| * |
| * @see #getV1SchemeIgnoredSigners() |
| */ |
| public List<V1SchemeSignerInfo> getV1SchemeSigners() { |
| return mV1SchemeSigners; |
| } |
| |
| /** |
| * Returns information about JAR signers ignored by the APK's signature verification |
| * process. These signers are ignored by Android. However, each signer's errors or warnings |
| * will contain information about why they are ignored. |
| * |
| * @see #getV1SchemeSigners() |
| */ |
| public List<V1SchemeSignerInfo> getV1SchemeIgnoredSigners() { |
| return mV1SchemeIgnoredSigners; |
| } |
| |
| /** |
| * Returns information about APK Signature Scheme v2 signers associated with the APK's |
| * signature. |
| */ |
| public List<V2SchemeSignerInfo> getV2SchemeSigners() { |
| return mV2SchemeSigners; |
| } |
| |
| /** |
| * Returns information about APK Signature Scheme v3 signers associated with the APK's |
| * signature. |
| * |
| * <note> Multiple signers represent different targeted platform versions, not |
| * a signing identity of multiple signers. APK Signature Scheme v3 only supports single |
| * signer identities.</note> |
| */ |
| public List<V3SchemeSignerInfo> getV3SchemeSigners() { |
| return mV3SchemeSigners; |
| } |
| |
| private List<V4SchemeSignerInfo> getV4SchemeSigners() { |
| return mV4SchemeSigners; |
| } |
| |
| /** |
| * Returns information about SourceStamp associated with the APK's signature. |
| */ |
| public SourceStampInfo getSourceStampInfo() { |
| return mSourceStampInfo; |
| } |
| |
| /** |
| * Returns the combined SigningCertificateLineage associated with this APK's APK Signature |
| * Scheme v3 signing block. |
| */ |
| public SigningCertificateLineage getSigningCertificateLineage() { |
| return mSigningCertificateLineage; |
| } |
| |
| void addError(Issue msg, Object... parameters) { |
| mErrors.add(new IssueWithParams(msg, parameters)); |
| } |
| |
| void addWarning(Issue msg, Object... parameters) { |
| mWarnings.add(new IssueWithParams(msg, parameters)); |
| } |
| |
| /** |
| * 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(V1SchemeVerifier.Result source) { |
| mVerifiedUsingV1Scheme = source.verified; |
| mErrors.addAll(source.getErrors()); |
| mWarnings.addAll(source.getWarnings()); |
| for (V1SchemeVerifier.Result.SignerInfo signer : source.signers) { |
| mV1SchemeSigners.add(new V1SchemeSignerInfo(signer)); |
| } |
| for (V1SchemeVerifier.Result.SignerInfo signer : source.ignoredSigners) { |
| mV1SchemeIgnoredSigners.add(new V1SchemeSignerInfo(signer)); |
| } |
| } |
| |
| private void mergeFrom(ApkSigningBlockUtils.Result source) { |
| switch (source.signatureSchemeVersion) { |
| case ApkSigningBlockUtils.VERSION_APK_SIGNATURE_SCHEME_V2: |
| mVerifiedUsingV2Scheme = source.verified; |
| for (ApkSigningBlockUtils.Result.SignerInfo signer : source.signers) { |
| mV2SchemeSigners.add(new V2SchemeSignerInfo(signer)); |
| } |
| break; |
| case ApkSigningBlockUtils.VERSION_APK_SIGNATURE_SCHEME_V3: |
| mVerifiedUsingV3Scheme = source.verified; |
| for (ApkSigningBlockUtils.Result.SignerInfo signer : source.signers) { |
| mV3SchemeSigners.add(new V3SchemeSignerInfo(signer)); |
| } |
| mSigningCertificateLineage = source.signingCertificateLineage; |
| break; |
| case ApkSigningBlockUtils.VERSION_APK_SIGNATURE_SCHEME_V4: |
| mVerifiedUsingV4Scheme = source.verified; |
| for (ApkSigningBlockUtils.Result.SignerInfo signer : source.signers) { |
| mV4SchemeSigners.add(new V4SchemeSignerInfo(signer)); |
| } |
| break; |
| case ApkSigningBlockUtils.VERSION_SOURCE_STAMP: |
| mSourceStampVerified = source.verified; |
| if (!source.signers.isEmpty()) { |
| mSourceStampInfo = new SourceStampInfo(source.signers.get(0)); |
| } |
| break; |
| default: |
| throw new IllegalArgumentException("Unknown Signing Block Scheme Id"); |
| } |
| mErrors.addAll(source.getErrors()); |
| mWarnings.addAll(source.getWarnings()); |
| } |
| |
| /** |
| * 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 (!mV1SchemeSigners.isEmpty()) { |
| for (V1SchemeSignerInfo signer : mV1SchemeSigners) { |
| if (signer.containsErrors()) { |
| return true; |
| } |
| } |
| } |
| if (!mV2SchemeSigners.isEmpty()) { |
| for (V2SchemeSignerInfo signer : mV2SchemeSigners) { |
| if (signer.containsErrors()) { |
| return true; |
| } |
| } |
| } |
| if (!mV3SchemeSigners.isEmpty()) { |
| for (V3SchemeSignerInfo signer : mV3SchemeSigners) { |
| if (signer.containsErrors()) { |
| return true; |
| } |
| } |
| } |
| if (mSourceStampInfo != null && mSourceStampInfo.containsErrors()) { |
| return true; |
| } |
| |
| return false; |
| } |
| |
| /** |
| * Information about a JAR signer associated with the APK's signature. |
| */ |
| public static class V1SchemeSignerInfo { |
| private final String mName; |
| private final List<X509Certificate> mCertChain; |
| private final String mSignatureBlockFileName; |
| private final String mSignatureFileName; |
| |
| private final List<IssueWithParams> mErrors; |
| private final List<IssueWithParams> mWarnings; |
| |
| private V1SchemeSignerInfo(V1SchemeVerifier.Result.SignerInfo result) { |
| mName = result.name; |
| mCertChain = result.certChain; |
| mSignatureBlockFileName = result.signatureBlockFileName; |
| mSignatureFileName = result.signatureFileName; |
| mErrors = result.getErrors(); |
| mWarnings = result.getWarnings(); |
| } |
| |
| /** |
| * Returns a user-friendly name of the signer. |
| */ |
| public String getName() { |
| return mName; |
| } |
| |
| /** |
| * Returns the name of the JAR entry containing this signer's JAR signature block file. |
| */ |
| public String getSignatureBlockFileName() { |
| return mSignatureBlockFileName; |
| } |
| |
| /** |
| * Returns the name of the JAR entry containing this signer's JAR signature file. |
| */ |
| public String getSignatureFileName() { |
| return mSignatureFileName; |
| } |
| |
| /** |
| * 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 mCertChain.isEmpty() ? null : mCertChain.get(0); |
| } |
| |
| /** |
| * Returns the certificate chain for the signer's public key. The certificate containing |
| * the public key is first, followed by the certificate (if any) which issued the |
| * signing certificate, and so forth. An empty list may be returned if an error was |
| * encountered during verification (see {@link #containsErrors()}). |
| */ |
| public List<X509Certificate> getCertificateChain() { |
| return mCertChain; |
| } |
| |
| /** |
| * Returns {@code true} if an error was encountered while verifying this signer's JAR |
| * signature. Any error prevents the signer's signature from being considered verified. |
| */ |
| public boolean containsErrors() { |
| return !mErrors.isEmpty(); |
| } |
| |
| /** |
| * Returns errors encountered while verifying this signer's JAR signature. Any error |
| * prevents the signer's signature from being considered verified. |
| */ |
| public List<IssueWithParams> getErrors() { |
| return mErrors; |
| } |
| |
| /** |
| * Returns warnings encountered while verifying this signer's JAR signature. Warnings |
| * do not prevent the signer's signature from being considered verified. |
| */ |
| public List<IssueWithParams> getWarnings() { |
| return mWarnings; |
| } |
| |
| private void addError(Issue msg, Object... parameters) { |
| mErrors.add(new IssueWithParams(msg, parameters)); |
| } |
| } |
| |
| /** |
| * 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 final List<ApkSigningBlockUtils.Result.SignerInfo.ContentDigest> |
| mContentDigests; |
| |
| private V2SchemeSignerInfo(ApkSigningBlockUtils.Result.SignerInfo result) { |
| mIndex = result.index; |
| mCerts = result.certs; |
| mErrors = result.getErrors(); |
| mWarnings = result.getWarnings(); |
| mContentDigests = result.contentDigests; |
| } |
| |
| /** |
| * 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; |
| } |
| |
| private void addError(Issue msg, Object... parameters) { |
| mErrors.add(new IssueWithParams(msg, parameters)); |
| } |
| |
| public boolean containsErrors() { |
| return !mErrors.isEmpty(); |
| } |
| |
| public List<IssueWithParams> getErrors() { |
| return mErrors; |
| } |
| |
| public List<IssueWithParams> getWarnings() { |
| return mWarnings; |
| } |
| |
| public List<ApkSigningBlockUtils.Result.SignerInfo.ContentDigest> getContentDigests() { |
| return mContentDigests; |
| } |
| } |
| |
| /** |
| * Information about an APK Signature Scheme v3 signer associated with the APK's signature. |
| */ |
| public static class V3SchemeSignerInfo { |
| private final int mIndex; |
| private final List<X509Certificate> mCerts; |
| |
| private final List<IssueWithParams> mErrors; |
| private final List<IssueWithParams> mWarnings; |
| private final List<ApkSigningBlockUtils.Result.SignerInfo.ContentDigest> |
| mContentDigests; |
| |
| private V3SchemeSignerInfo(ApkSigningBlockUtils.Result.SignerInfo result) { |
| mIndex = result.index; |
| mCerts = result.certs; |
| mErrors = result.getErrors(); |
| mWarnings = result.getWarnings(); |
| mContentDigests = result.contentDigests; |
| } |
| |
| /** |
| * Returns this signer's {@code 0}-based index in the list of signers contained in the |
| * APK's APK Signature Scheme v3 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; |
| } |
| |
| public List<ApkSigningBlockUtils.Result.SignerInfo.ContentDigest> getContentDigests() { |
| return mContentDigests; |
| } |
| } |
| |
| /** |
| * Information about an APK Signature Scheme V4 signer associated with the APK's |
| * signature. |
| */ |
| public static class V4SchemeSignerInfo { |
| private final int mIndex; |
| private final List<X509Certificate> mCerts; |
| |
| private final List<IssueWithParams> mErrors; |
| private final List<IssueWithParams> mWarnings; |
| private final List<ApkSigningBlockUtils.Result.SignerInfo.ContentDigest> |
| mContentDigests; |
| |
| private V4SchemeSignerInfo(ApkSigningBlockUtils.Result.SignerInfo result) { |
| mIndex = result.index; |
| mCerts = result.certs; |
| mErrors = result.getErrors(); |
| mWarnings = result.getWarnings(); |
| mContentDigests = result.contentDigests; |
| } |
| |
| /** |
| * Returns this signer's {@code 0}-based index in the list of signers contained in the |
| * APK's APK Signature Scheme v3 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; |
| } |
| |
| public List<ApkSigningBlockUtils.Result.SignerInfo.ContentDigest> getContentDigests() { |
| return mContentDigests; |
| } |
| } |
| |
| /** |
| * Information about SourceStamp associated with the APK's signature. |
| */ |
| public static class SourceStampInfo { |
| private final List<X509Certificate> mCertificates; |
| |
| private final List<IssueWithParams> mErrors; |
| private final List<IssueWithParams> mWarnings; |
| |
| private SourceStampInfo(ApkSigningBlockUtils.Result.SignerInfo result) { |
| mCertificates = result.certs; |
| mErrors = result.getErrors(); |
| mWarnings = result.getWarnings(); |
| } |
| |
| /** |
| * Returns the SourceStamp'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 SourceStamp's public key. |
| */ |
| public X509Certificate getCertificate() { |
| return mCertificates.isEmpty() ? null : mCertificates.get(0); |
| } |
| |
| 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 enum Issue { |
| |
| /** |
| * APK is not JAR-signed. |
| */ |
| JAR_SIG_NO_SIGNATURES("No JAR signatures"), |
| |
| /** |
| * APK does not contain any entries covered by JAR signatures. |
| */ |
| JAR_SIG_NO_SIGNED_ZIP_ENTRIES("No JAR entries covered by JAR signatures"), |
| |
| /** |
| * APK contains multiple entries with the same name. |
| * |
| * <ul> |
| * <li>Parameter 1: name ({@code String})</li> |
| * </ul> |
| */ |
| JAR_SIG_DUPLICATE_ZIP_ENTRY("Duplicate entry: %1$s"), |
| |
| /** |
| * JAR manifest contains a section with a duplicate name. |
| * |
| * <ul> |
| * <li>Parameter 1: section name ({@code String})</li> |
| * </ul> |
| */ |
| JAR_SIG_DUPLICATE_MANIFEST_SECTION("Duplicate section in META-INF/MANIFEST.MF: %1$s"), |
| |
| /** |
| * JAR manifest contains a section without a name. |
| * |
| * <ul> |
| * <li>Parameter 1: section index (1-based) ({@code Integer})</li> |
| * </ul> |
| */ |
| JAR_SIG_UNNNAMED_MANIFEST_SECTION( |
| "Malformed META-INF/MANIFEST.MF: invidual section #%1$d does not have a name"), |
| |
| /** |
| * JAR signature file contains a section without a name. |
| * |
| * <ul> |
| * <li>Parameter 1: signature file name ({@code String})</li> |
| * <li>Parameter 2: section index (1-based) ({@code Integer})</li> |
| * </ul> |
| */ |
| JAR_SIG_UNNNAMED_SIG_FILE_SECTION( |
| "Malformed %1$s: invidual section #%2$d does not have a name"), |
| |
| /** APK is missing the JAR manifest entry (META-INF/MANIFEST.MF). */ |
| JAR_SIG_NO_MANIFEST("Missing META-INF/MANIFEST.MF"), |
| |
| /** |
| * JAR manifest references an entry which is not there in the APK. |
| * |
| * <ul> |
| * <li>Parameter 1: entry name ({@code String})</li> |
| * </ul> |
| */ |
| JAR_SIG_MISSING_ZIP_ENTRY_REFERENCED_IN_MANIFEST( |
| "%1$s entry referenced by META-INF/MANIFEST.MF not found in the APK"), |
| |
| /** |
| * JAR manifest does not list a digest for the specified entry. |
| * |
| * <ul> |
| * <li>Parameter 1: entry name ({@code String})</li> |
| * </ul> |
| */ |
| JAR_SIG_NO_ZIP_ENTRY_DIGEST_IN_MANIFEST("No digest for %1$s in META-INF/MANIFEST.MF"), |
| |
| /** |
| * JAR signature does not list a digest for the specified entry. |
| * |
| * <ul> |
| * <li>Parameter 1: entry name ({@code String})</li> |
| * <li>Parameter 2: signature file name ({@code String})</li> |
| * </ul> |
| */ |
| JAR_SIG_NO_ZIP_ENTRY_DIGEST_IN_SIG_FILE("No digest for %1$s in %2$s"), |
| |
| /** |
| * The specified JAR entry is not covered by JAR signature. |
| * |
| * <ul> |
| * <li>Parameter 1: entry name ({@code String})</li> |
| * </ul> |
| */ |
| JAR_SIG_ZIP_ENTRY_NOT_SIGNED("%1$s entry not signed"), |
| |
| /** |
| * JAR signature uses different set of signers to protect the two specified ZIP entries. |
| * |
| * <ul> |
| * <li>Parameter 1: first entry name ({@code String})</li> |
| * <li>Parameter 2: first entry signer names ({@code List<String>})</li> |
| * <li>Parameter 3: second entry name ({@code String})</li> |
| * <li>Parameter 4: second entry signer names ({@code List<String>})</li> |
| * </ul> |
| */ |
| JAR_SIG_ZIP_ENTRY_SIGNERS_MISMATCH( |
| "Entries %1$s and %3$s are signed with different sets of signers" |
| + " : <%2$s> vs <%4$s>"), |
| |
| /** |
| * Digest of the specified ZIP entry's data does not match the digest expected by the JAR |
| * signature. |
| * |
| * <ul> |
| * <li>Parameter 1: entry name ({@code String})</li> |
| * <li>Parameter 2: digest algorithm (e.g., SHA-256) ({@code String})</li> |
| * <li>Parameter 3: name of the entry in which the expected digest is specified |
| * ({@code String})</li> |
| * <li>Parameter 4: base64-encoded actual digest ({@code String})</li> |
| * <li>Parameter 5: base64-encoded expected digest ({@code String})</li> |
| * </ul> |
| */ |
| JAR_SIG_ZIP_ENTRY_DIGEST_DID_NOT_VERIFY( |
| "%2$s digest of %1$s does not match the digest specified in %3$s" |
| + ". Expected: <%5$s>, actual: <%4$s>"), |
| |
| /** |
| * Digest of the JAR manifest main section did not verify. |
| * |
| * <ul> |
| * <li>Parameter 1: digest algorithm (e.g., SHA-256) ({@code String})</li> |
| * <li>Parameter 2: name of the entry in which the expected digest is specified |
| * ({@code String})</li> |
| * <li>Parameter 3: base64-encoded actual digest ({@code String})</li> |
| * <li>Parameter 4: base64-encoded expected digest ({@code String})</li> |
| * </ul> |
| */ |
| JAR_SIG_MANIFEST_MAIN_SECTION_DIGEST_DID_NOT_VERIFY( |
| "%1$s digest of META-INF/MANIFEST.MF main section does not match the digest" |
| + " specified in %2$s. Expected: <%4$s>, actual: <%3$s>"), |
| |
| /** |
| * Digest of the specified JAR manifest section does not match the digest expected by the |
| * JAR signature. |
| * |
| * <ul> |
| * <li>Parameter 1: section name ({@code String})</li> |
| * <li>Parameter 2: digest algorithm (e.g., SHA-256) ({@code String})</li> |
| * <li>Parameter 3: name of the signature file in which the expected digest is specified |
| * ({@code String})</li> |
| * <li>Parameter 4: base64-encoded actual digest ({@code String})</li> |
| * <li>Parameter 5: base64-encoded expected digest ({@code String})</li> |
| * </ul> |
| */ |
| JAR_SIG_MANIFEST_SECTION_DIGEST_DID_NOT_VERIFY( |
| "%2$s digest of META-INF/MANIFEST.MF section for %1$s does not match the digest" |
| + " specified in %3$s. Expected: <%5$s>, actual: <%4$s>"), |
| |
| /** |
| * JAR signature file does not contain the whole-file digest of the JAR manifest file. The |
| * digest speeds up verification of JAR signature. |
| * |
| * <ul> |
| * <li>Parameter 1: name of the signature file ({@code String})</li> |
| * </ul> |
| */ |
| JAR_SIG_NO_MANIFEST_DIGEST_IN_SIG_FILE( |
| "%1$s does not specify digest of META-INF/MANIFEST.MF" |
| + ". This slows down verification."), |
| |
| /** |
| * APK is signed using APK Signature Scheme v2 or newer, but JAR signature file does not |
| * contain protections against stripping of these newer scheme signatures. |
| * |
| * <ul> |
| * <li>Parameter 1: name of the signature file ({@code String})</li> |
| * </ul> |
| */ |
| JAR_SIG_NO_APK_SIG_STRIP_PROTECTION( |
| "APK is signed using APK Signature Scheme v2 but these signatures may be stripped" |
| + " without being detected because %1$s does not contain anti-stripping" |
| + " protections."), |
| |
| /** |
| * JAR signature of the signer is missing a file/entry. |
| * |
| * <ul> |
| * <li>Parameter 1: name of the encountered file ({@code String})</li> |
| * <li>Parameter 2: name of the missing file ({@code String})</li> |
| * </ul> |
| */ |
| JAR_SIG_MISSING_FILE("Partial JAR signature. Found: %1$s, missing: %2$s"), |
| |
| /** |
| * An exception was encountered while verifying JAR signature contained in a signature block |
| * against the signature file. |
| * |
| * <ul> |
| * <li>Parameter 1: name of the signature block file ({@code String})</li> |
| * <li>Parameter 2: name of the signature file ({@code String})</li> |
| * <li>Parameter 3: exception ({@code Throwable})</li> |
| * </ul> |
| */ |
| JAR_SIG_VERIFY_EXCEPTION("Failed to verify JAR signature %1$s against %2$s: %3$s"), |
| |
| /** |
| * JAR signature contains unsupported digest algorithm. |
| * |
| * <ul> |
| * <li>Parameter 1: name of the signature block file ({@code String})</li> |
| * <li>Parameter 2: digest algorithm OID ({@code String})</li> |
| * <li>Parameter 3: signature algorithm OID ({@code String})</li> |
| * <li>Parameter 4: API Levels on which this combination of algorithms is not supported |
| * ({@code String})</li> |
| * <li>Parameter 5: user-friendly variant of digest algorithm ({@code String})</li> |
| * <li>Parameter 6: user-friendly variant of signature algorithm ({@code String})</li> |
| * </ul> |
| */ |
| JAR_SIG_UNSUPPORTED_SIG_ALG( |
| "JAR signature %1$s uses digest algorithm %5$s and signature algorithm %6$s which" |
| + " is not supported on API Level(s) %4$s for which this APK is being" |
| + " verified"), |
| |
| /** |
| * An exception was encountered while parsing JAR signature contained in a signature block. |
| * |
| * <ul> |
| * <li>Parameter 1: name of the signature block file ({@code String})</li> |
| * <li>Parameter 2: exception ({@code Throwable})</li> |
| * </ul> |
| */ |
| JAR_SIG_PARSE_EXCEPTION("Failed to parse JAR signature %1$s: %2$s"), |
| |
| /** |
| * An exception was encountered while parsing a certificate contained in the JAR signature |
| * block. |
| * |
| * <ul> |
| * <li>Parameter 1: name of the signature block file ({@code String})</li> |
| * <li>Parameter 2: exception ({@code Throwable})</li> |
| * </ul> |
| */ |
| JAR_SIG_MALFORMED_CERTIFICATE("Malformed certificate in JAR signature %1$s: %2$s"), |
| |
| /** |
| * JAR signature contained in a signature block file did not verify against the signature |
| * file. |
| * |
| * <ul> |
| * <li>Parameter 1: name of the signature block file ({@code String})</li> |
| * <li>Parameter 2: name of the signature file ({@code String})</li> |
| * </ul> |
| */ |
| JAR_SIG_DID_NOT_VERIFY("JAR signature %1$s did not verify against %2$s"), |
| |
| /** |
| * JAR signature contains no verified signers. |
| * |
| * <ul> |
| * <li>Parameter 1: name of the signature block file ({@code String})</li> |
| * </ul> |
| */ |
| JAR_SIG_NO_SIGNERS("JAR signature %1$s contains no signers"), |
| |
| /** |
| * JAR signature file contains a section with a duplicate name. |
| * |
| * <ul> |
| * <li>Parameter 1: signature file name ({@code String})</li> |
| * <li>Parameter 1: section name ({@code String})</li> |
| * </ul> |
| */ |
| JAR_SIG_DUPLICATE_SIG_FILE_SECTION("Duplicate section in %1$s: %2$s"), |
| |
| /** |
| * JAR signature file's main section doesn't contain the mandatory Signature-Version |
| * attribute. |
| * |
| * <ul> |
| * <li>Parameter 1: signature file name ({@code String})</li> |
| * </ul> |
| */ |
| JAR_SIG_MISSING_VERSION_ATTR_IN_SIG_FILE( |
| "Malformed %1$s: missing Signature-Version attribute"), |
| |
| /** |
| * JAR signature file references an unknown APK signature scheme ID. |
| * |
| * <ul> |
| * <li>Parameter 1: name of the signature file ({@code String})</li> |
| * <li>Parameter 2: unknown APK signature scheme ID ({@code} Integer)</li> |
| * </ul> |
| */ |
| JAR_SIG_UNKNOWN_APK_SIG_SCHEME_ID( |
| "JAR signature %1$s references unknown APK signature scheme ID: %2$d"), |
| |
| /** |
| * JAR signature file indicates that the APK is supposed to be signed with a supported APK |
| * signature scheme (in addition to the JAR signature) but no such signature was found in |
| * the APK. |
| * |
| * <ul> |
| * <li>Parameter 1: name of the signature file ({@code String})</li> |
| * <li>Parameter 2: APK signature scheme ID ({@code} Integer)</li> |
| * <li>Parameter 3: APK signature scheme English name ({@code} String)</li> |
| * </ul> |
| */ |
| JAR_SIG_MISSING_APK_SIG_REFERENCED( |
| "JAR signature %1$s indicates the APK is signed using %3$s but no such signature" |
| + " was found. Signature stripped?"), |
| |
| /** |
| * JAR entry is not covered by signature and thus unauthorized modifications to its contents |
| * will not be detected. |
| * |
| * <ul> |
| * <li>Parameter 1: entry name ({@code String})</li> |
| * </ul> |
| */ |
| JAR_SIG_UNPROTECTED_ZIP_ENTRY( |
| "%1$s not protected by signature. Unauthorized modifications to this JAR entry" |
| + " will not be detected. Delete or move the entry outside of META-INF/."), |
| |
| /** |
| * APK which is both JAR-signed and signed using APK Signature Scheme v2 contains an APK |
| * Signature Scheme v2 signature from this signer, but does not contain a JAR signature |
| * from this signer. |
| */ |
| JAR_SIG_MISSING("No JAR signature from this signer"), |
| |
| /** |
| * APK is targeting a sandbox version which requires APK Signature Scheme v2 signature but |
| * no such signature was found. |
| * |
| * <ul> |
| * <li>Parameter 1: target sandbox version ({@code Integer})</li> |
| * </ul> |
| */ |
| NO_SIG_FOR_TARGET_SANDBOX_VERSION( |
| "Missing APK Signature Scheme v2 signature required for target sandbox version" |
| + " %1$d"), |
| |
| /** |
| * APK is targeting an SDK version that requires a minimum signature scheme version, but the |
| * APK is not signed with that version or later. |
| * |
| * <ul> |
| * <li>Parameter 1: target SDK Version (@code Integer})</li> |
| * <li>Parameter 2: minimum signature scheme version ((@code Integer})</li> |
| * </ul> |
| */ |
| MIN_SIG_SCHEME_FOR_TARGET_SDK_NOT_MET( |
| "Target SDK version %1$d requires a minimum of signature scheme v%2$d; the APK is" |
| + " not signed with this or a later signature scheme"), |
| |
| /** |
| * APK which is both JAR-signed and signed using APK Signature Scheme v2 contains a JAR |
| * signature from this signer, but does not contain an APK Signature Scheme v2 signature |
| * from this signer. |
| */ |
| V2_SIG_MISSING("No APK Signature Scheme v2 signature from this signer"), |
| |
| /** |
| * 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 references an unknown APK signature scheme ID. |
| * |
| * <ul> |
| * <li>Parameter 1: signer index ({@code Integer})</li> |
| * <li>Parameter 2: unknown APK signature scheme ID ({@code} Integer)</li> |
| * </ul> |
| */ |
| V2_SIG_UNKNOWN_APK_SIG_SCHEME_ID( |
| "APK Signature Scheme v2 signer: %1$s references unknown APK signature scheme ID: " |
| + "%2$d"), |
| |
| /** |
| * APK Signature Scheme v2 signature indicates that the APK is supposed to be signed with a |
| * supported APK signature scheme (in addition to the v2 signature) but no such signature |
| * was found in the APK. |
| * |
| * <ul> |
| * <li>Parameter 1: signer index ({@code Integer})</li> |
| * <li>Parameter 2: APK signature scheme English name ({@code} String)</li> |
| * </ul> |
| */ |
| V2_SIG_MISSING_APK_SIG_REFERENCED( |
| "APK Signature Scheme v2 signature %1$s indicates the APK is signed using %2$s but " |
| + "no such signature was found. Signature stripped?"), |
| |
| /** |
| * 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: %1$s"), |
| |
| /** |
| * 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>"), |
| |
| /** |
| * Failed to parse the list of signers contained in the APK Signature Scheme v3 signature. |
| */ |
| V3_SIG_MALFORMED_SIGNERS("Malformed list of signers"), |
| |
| /** |
| * Failed to parse this signer's signer block contained in the APK Signature Scheme v3 |
| * signature. |
| */ |
| V3_SIG_MALFORMED_SIGNER("Malformed signer block"), |
| |
| /** |
| * Public key embedded in the APK Signature Scheme v3 signature of this signer could not be |
| * parsed. |
| * |
| * <ul> |
| * <li>Parameter 1: error details ({@code Throwable})</li> |
| * </ul> |
| */ |
| V3_SIG_MALFORMED_PUBLIC_KEY("Malformed public key: %1$s"), |
| |
| /** |
| * This APK Signature Scheme v3 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> |
| */ |
| V3_SIG_MALFORMED_CERTIFICATE("Malformed certificate #%2$d: %3$s"), |
| |
| /** |
| * Failed to parse this signer's signature record contained in the APK Signature Scheme v3 |
| * signature. |
| * |
| * <ul> |
| * <li>Parameter 1: record number (first record is {@code 1}) ({@code Integer})</li> |
| * </ul> |
| */ |
| V3_SIG_MALFORMED_SIGNATURE("Malformed APK Signature Scheme v3 signature record #%1$d"), |
| |
| /** |
| * Failed to parse this signer's digest record contained in the APK Signature Scheme v3 |
| * signature. |
| * |
| * <ul> |
| * <li>Parameter 1: record number (first record is {@code 1}) ({@code Integer})</li> |
| * </ul> |
| */ |
| V3_SIG_MALFORMED_DIGEST("Malformed APK Signature Scheme v3 digest record #%1$d"), |
| |
| /** |
| * This APK Signature Scheme v3 signer contains a malformed additional attribute. |
| * |
| * <ul> |
| * <li>Parameter 1: attribute number (first attribute is {@code 1}) {@code Integer})</li> |
| * </ul> |
| */ |
| V3_SIG_MALFORMED_ADDITIONAL_ATTRIBUTE("Malformed additional attribute #%1$d"), |
| |
| /** |
| * APK Signature Scheme v3 signature contains no signers. |
| */ |
| V3_SIG_NO_SIGNERS("No signers in APK Signature Scheme v3 signature"), |
| |
| /** |
| * APK Signature Scheme v3 signature contains multiple signers (only one allowed per |
| * platform version). |
| */ |
| V3_SIG_MULTIPLE_SIGNERS("Multiple APK Signature Scheme v3 signatures found for a single " |
| + " platform version."), |
| |
| /** |
| * APK Signature Scheme v3 signature found, but multiple v1 and/or multiple v2 signers |
| * found, where only one may be used with APK Signature Scheme v3 |
| */ |
| V3_SIG_MULTIPLE_PAST_SIGNERS("Multiple signatures found for pre-v3 signing with an APK " |
| + " Signature Scheme v3 signer. Only one allowed."), |
| |
| /** |
| * APK Signature Scheme v3 signature found, but its signer doesn't match the v1/v2 signers, |
| * or have them as the root of its signing certificate history |
| */ |
| V3_SIG_PAST_SIGNERS_MISMATCH( |
| "v3 signer differs from v1/v2 signer without proper signing certificate lineage."), |
| |
| /** |
| * This APK Signature Scheme v3 signer contains a signature produced using an unknown |
| * algorithm. |
| * |
| * <ul> |
| * <li>Parameter 1: algorithm ID ({@code Integer})</li> |
| * </ul> |
| */ |
| V3_SIG_UNKNOWN_SIG_ALGORITHM("Unknown signature algorithm: %1$#x"), |
| |
| /** |
| * This APK Signature Scheme v3 signer contains an unknown additional attribute. |
| * |
| * <ul> |
| * <li>Parameter 1: attribute ID ({@code Integer})</li> |
| * </ul> |
| */ |
| V3_SIG_UNKNOWN_ADDITIONAL_ATTRIBUTE("Unknown additional attribute: ID %1$#x"), |
| |
| /** |
| * An exception was encountered while verifying APK Signature Scheme v3 signature of this |
| * signer. |
| * |
| * <ul> |
| * <li>Parameter 1: signature algorithm ({@link SignatureAlgorithm})</li> |
| * <li>Parameter 2: exception ({@code Throwable})</li> |
| * </ul> |
| */ |
| V3_SIG_VERIFY_EXCEPTION("Failed to verify %1$s signature: %2$s"), |
| |
| /** |
| * The APK Signature Scheme v3 signer contained an invalid value for either min or max SDK |
| * versions. |
| * |
| * <ul> |
| * <li>Parameter 1: minSdkVersion ({@code Integer}) |
| * <li>Parameter 2: maxSdkVersion ({@code Integer}) |
| * </ul> |
| */ |
| V3_SIG_INVALID_SDK_VERSIONS("Invalid SDK Version parameter(s) encountered in APK Signature " |
| + "scheme v3 signature: minSdkVersion %1$s maxSdkVersion: %2$s"), |
| |
| /** |
| * APK Signature Scheme v3 signature over this signer's signed-data block did not verify. |
| * |
| * <ul> |
| * <li>Parameter 1: signature algorithm ({@link SignatureAlgorithm})</li> |
| * </ul> |
| */ |
| V3_SIG_DID_NOT_VERIFY("%1$s signature over signed-data did not verify"), |
| |
| /** |
| * This APK Signature Scheme v3 signer offers no signatures. |
| */ |
| V3_SIG_NO_SIGNATURES("No signatures"), |
| |
| /** |
| * This APK Signature Scheme v3 signer offers signatures but none of them are supported. |
| */ |
| V3_SIG_NO_SUPPORTED_SIGNATURES("No supported signatures"), |
| |
| /** |
| * This APK Signature Scheme v3 signer offers no certificates. |
| */ |
| V3_SIG_NO_CERTIFICATES("No certificates"), |
| |
| /** |
| * This APK Signature Scheme v3 signer's minSdkVersion listed in the signer's signed data |
| * does not match the minSdkVersion listed in the signatures record. |
| * |
| * <ul> |
| * <li>Parameter 1: minSdkVersion in signature record ({@code Integer}) </li> |
| * <li>Parameter 2: minSdkVersion in signed data ({@code Integer}) </li> |
| * </ul> |
| */ |
| V3_MIN_SDK_VERSION_MISMATCH_BETWEEN_SIGNER_AND_SIGNED_DATA_RECORD( |
| "minSdkVersion mismatch between signed data and signature record:" |
| + " <%1$s> vs <%2$s>"), |
| |
| /** |
| * This APK Signature Scheme v3 signer's maxSdkVersion listed in the signer's signed data |
| * does not match the maxSdkVersion listed in the signatures record. |
| * |
| * <ul> |
| * <li>Parameter 1: maxSdkVersion in signature record ({@code Integer}) </li> |
| * <li>Parameter 2: maxSdkVersion in signed data ({@code Integer}) </li> |
| * </ul> |
| */ |
| V3_MAX_SDK_VERSION_MISMATCH_BETWEEN_SIGNER_AND_SIGNED_DATA_RECORD( |
| "maxSdkVersion mismatch between signed data and signature record:" |
| + " <%1$s> vs <%2$s>"), |
| |
| /** |
| * This APK Signature Scheme v3 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> |
| */ |
| V3_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 v3 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> |
| */ |
| V3_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 v3 |
| * 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> |
| */ |
| V3_SIG_APK_DIGEST_DID_NOT_VERIFY( |
| "APK integrity check failed. %1$s digest mismatch." |
| + " Expected: <%2$s>, actual: <%3$s>"), |
| |
| /** |
| * The signer's SigningCertificateLineage attribute containd a proof-of-rotation record with |
| * signature(s) that did not verify. |
| */ |
| V3_SIG_POR_DID_NOT_VERIFY("SigningCertificateLineage attribute containd a proof-of-rotation" |
| + " record with signature(s) that did not verify."), |
| |
| /** |
| * Failed to parse the SigningCertificateLineage structure in the APK Signature Scheme v3 |
| * signature's additional attributes section. |
| */ |
| V3_SIG_MALFORMED_LINEAGE("Failed to parse the SigningCertificateLineage structure in the " |
| + "APK Signature Scheme v3 signature's additional attributes section."), |
| |
| /** |
| * The APK's signing certificate does not match the terminal node in the provided |
| * proof-of-rotation structure describing the signing certificate history |
| */ |
| V3_SIG_POR_CERT_MISMATCH( |
| "APK signing certificate differs from the associated certificate found in the " |
| + "signer's SigningCertificateLineage."), |
| |
| /** |
| * The APK Signature Scheme v3 signers encountered do not offer a continuous set of |
| * supported platform versions. Either they overlap, resulting in potentially two |
| * acceptable signers for a platform version, or there are holes which would create problems |
| * in the event of platform version upgrades. |
| */ |
| V3_INCONSISTENT_SDK_VERSIONS("APK Signature Scheme v3 signers supported min/max SDK " |
| + "versions are not continuous."), |
| |
| /** |
| * The APK Signature Scheme v3 signers don't cover all requested SDK versions. |
| * |
| * <ul> |
| * <li>Parameter 1: minSdkVersion ({@code Integer}) |
| * <li>Parameter 2: maxSdkVersion ({@code Integer}) |
| * </ul> |
| */ |
| V3_MISSING_SDK_VERSIONS("APK Signature Scheme v3 signers supported min/max SDK " |
| + "versions do not cover the entire desired range. Found min: %1$s max %2$s"), |
| |
| /** |
| * The SigningCertificateLineages for different platform versions using APK Signature Scheme |
| * v3 do not go together. Specifically, each should be a subset of another, with the size |
| * of each increasing as the platform level increases. |
| */ |
| V3_INCONSISTENT_LINEAGES("SigningCertificateLineages targeting different platform versions" |
| + " using APK Signature Scheme v3 are not all a part of the same overall lineage."), |
| |
| /** |
| * 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"), |
| |
| /** |
| * Failed to parse this signer's signature record contained in the APK Signature Scheme |
| * V4 signature. |
| * |
| * <ul> |
| * <li>Parameter 1: record number (first record is {@code 1}) ({@code Integer})</li> |
| * </ul> |
| */ |
| V4_SIG_MALFORMED_SIGNERS( |
| "V4 signature has malformed signer block"), |
| |
| /** |
| * This APK Signature Scheme V4 signer contains a signature produced using an |
| * unknown algorithm. |
| * |
| * <ul> |
| * <li>Parameter 1: algorithm ID ({@code Integer})</li> |
| * </ul> |
| */ |
| V4_SIG_UNKNOWN_SIG_ALGORITHM( |
| "V4 signature has unknown signing algorithm: %1$#x"), |
| |
| /** |
| * This APK Signature Scheme V4 signer offers no signatures. |
| */ |
| V4_SIG_NO_SIGNATURES( |
| "V4 signature has no signature found"), |
| |
| /** |
| * This APK Signature Scheme V4 signer offers signatures but none of them are |
| * supported. |
| */ |
| V4_SIG_NO_SUPPORTED_SIGNATURES( |
| "V4 signature has no supported signature"), |
| |
| /** |
| * APK Signature Scheme v3 signature over this signer's signed-data block did not verify. |
| * |
| * <ul> |
| * <li>Parameter 1: signature algorithm ({@link SignatureAlgorithm})</li> |
| * </ul> |
| */ |
| V4_SIG_DID_NOT_VERIFY("%1$s signature over signed-data did not verify"), |
| |
| /** |
| * An exception was encountered while verifying APK Signature Scheme v3 signature of this |
| * signer. |
| * |
| * <ul> |
| * <li>Parameter 1: signature algorithm ({@link SignatureAlgorithm})</li> |
| * <li>Parameter 2: exception ({@code Throwable})</li> |
| * </ul> |
| */ |
| V4_SIG_VERIFY_EXCEPTION("Failed to verify %1$s signature: %2$s"), |
| |
| /** |
| * Public key embedded in the APK Signature Scheme v4 signature of this signer could not be |
| * parsed. |
| * |
| * <ul> |
| * <li>Parameter 1: error details ({@code Throwable})</li> |
| * </ul> |
| */ |
| V4_SIG_MALFORMED_PUBLIC_KEY("Malformed public key: %1$s"), |
| |
| /** |
| * This APK Signature Scheme V4 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> |
| */ |
| V4_SIG_MALFORMED_CERTIFICATE( |
| "V4 signature has malformed certificate"), |
| |
| /** |
| * This APK Signature Scheme V4 signer offers no certificate. |
| */ |
| V4_SIG_NO_CERTIFICATE("V4 signature has no certificate"), |
| |
| /** |
| * This APK Signature Scheme V4 signer's public key listed in the signer's |
| * certificate does not match the public key listed in the signature proto. |
| * |
| * <ul> |
| * <li>Parameter 1: hex-encoded public key from certificate ({@code String})</li> |
| * <li>Parameter 2: hex-encoded public key from signature proto ({@code String})</li> |
| * </ul> |
| */ |
| V4_SIG_PUBLIC_KEY_MISMATCH_BETWEEN_CERTIFICATE_AND_SIGNATURES_RECORD( |
| "V4 signature has mismatched certificate and signature: <%1$s> vs <%2$s>"), |
| |
| /** |
| * The APK's hash root (aka digest) does not match the hash root contained in the Signature |
| * Scheme V4 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> |
| */ |
| V4_SIG_APK_ROOT_DID_NOT_VERIFY( |
| "V4 signature's hash tree root (content digest) did not verity"), |
| |
| /** |
| * The APK's hash tree does not match the hash tree contained in the Signature |
| * Scheme V4 signature. |
| * |
| * <ul> |
| * <li>Parameter 1: content digest algorithm ({@link ContentDigestAlgorithm})</li> |
| * <li>Parameter 2: hex-encoded expected hash tree of the APK ({@code String})</li> |
| * <li>Parameter 3: hex-encoded actual hash tree of the APK ({@code String})</li> |
| * </ul> |
| */ |
| V4_SIG_APK_TREE_DID_NOT_VERIFY( |
| "V4 signature's hash tree did not verity"), |
| |
| /** |
| * Using more than one Signer to sign APK Signature Scheme V4 signature. |
| */ |
| V4_SIG_MULTIPLE_SIGNERS( |
| "V4 signature only supports one signer"), |
| |
| /** |
| * The signer used to sign APK Signature Scheme V2/V3 signature does not match the signer |
| * used to sign APK Signature Scheme V4 signature. |
| */ |
| V4_SIG_V2_V3_SIGNERS_MISMATCH( |
| "V4 signature and V2/V3 signature have mismatched certificates"), |
| |
| V4_SIG_V2_V3_DIGESTS_MISMATCH( |
| "V4 signature and V2/V3 signature have mismatched digests"), |
| |
| /** |
| * The v4 signature format version isn't the same as the tool's current version, something |
| * may go wrong. |
| */ |
| V4_SIG_VERSION_NOT_CURRENT( |
| "V4 signature format version %1$d is different from the tool's current " |
| + "version %2$d"), |
| |
| /** APK contains SourceStamp file, but does not contain a SourceStamp signature. */ |
| SOURCE_STAMP_SIG_MISSING("No SourceStamp signature"), |
| |
| /** |
| * SourceStamp's certificate could not be parsed. |
| * |
| * <ul> |
| * <li>Parameter 1: error details ({@code Throwable}) |
| * </ul> |
| */ |
| SOURCE_STAMP_MALFORMED_CERTIFICATE("Malformed certificate: %1$s"), |
| |
| /** Failed to parse SourceStamp's signature. */ |
| SOURCE_STAMP_MALFORMED_SIGNATURE("Malformed SourceStamp signature"), |
| |
| /** |
| * SourceStamp contains a signature produced using an unknown algorithm. |
| * |
| * <ul> |
| * <li>Parameter 1: algorithm ID ({@code Integer}) |
| * </ul> |
| */ |
| SOURCE_STAMP_UNKNOWN_SIG_ALGORITHM("Unknown signature algorithm: %1$#x"), |
| |
| /** |
| * An exception was encountered while verifying SourceStamp signature. |
| * |
| * <ul> |
| * <li>Parameter 1: signature algorithm ({@link SignatureAlgorithm}) |
| * <li>Parameter 2: exception ({@code Throwable}) |
| * </ul> |
| */ |
| SOURCE_STAMP_VERIFY_EXCEPTION("Failed to verify %1$s signature: %2$s"), |
| |
| /** |
| * SourceStamp signature block did not verify. |
| * |
| * <ul> |
| * <li>Parameter 1: signature algorithm ({@link SignatureAlgorithm}) |
| * </ul> |
| */ |
| SOURCE_STAMP_DID_NOT_VERIFY("%1$s signature over signed-data did not verify"), |
| |
| /** SourceStamp offers no signatures. */ |
| SOURCE_STAMP_NO_SIGNATURE("No signature"), |
| |
| /** SourceStamp offers an unsupported signature. */ |
| SOURCE_STAMP_NO_SUPPORTED_SIGNATURE("Signature not supported"), |
| |
| /** |
| * SourceStamp's certificate listed in the APK signing block does not match the certificate |
| * listed in the SourceStamp file in the APK. |
| * |
| * <ul> |
| * <li>Parameter 1: SHA-256 hash of certificate from SourceStamp block in APK signing |
| * block ({@code String}) |
| * <li>Parameter 2: SHA-256 hash of certificate from SourceStamp file in APK ({@code |
| * String}) |
| * </ul> |
| */ |
| SOURCE_STAMP_CERTIFICATE_MISMATCH_BETWEEN_SIGNATURE_BLOCK_AND_APK( |
| "Certificate mismatch between SourceStamp block in APK signing block and" |
| + " SourceStamp file in APK: <%1$s> vs <%2$s>"); |
| |
| private final String mFormat; |
| |
| 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); |
| } |
| } |
| |
| /** |
| * Wrapped around {@code byte[]} which ensures that {@code equals} and {@code hashCode} operate |
| * on the contents of the arrays rather than on references. |
| */ |
| private static class ByteArray { |
| private final byte[] mArray; |
| private final int mHashCode; |
| |
| private ByteArray(byte[] arr) { |
| mArray = arr; |
| mHashCode = Arrays.hashCode(mArray); |
| } |
| |
| @Override |
| public int hashCode() { |
| return mHashCode; |
| } |
| |
| @Override |
| public boolean equals(Object obj) { |
| if (this == obj) { |
| return true; |
| } |
| if (!(obj instanceof ByteArray)) { |
| return false; |
| } |
| ByteArray other = (ByteArray) obj; |
| if (hashCode() != other.hashCode()) { |
| return false; |
| } |
| if (!Arrays.equals(mArray, other.mArray)) { |
| return false; |
| } |
| return true; |
| } |
| } |
| |
| /** |
| * Builder of {@link ApkVerifier} instances. |
| * |
| * <p>The resulting verifier by default checks whether the APK will verify on all platform |
| * versions supported by the APK, as specified by {@code android:minSdkVersion} attributes in |
| * the APK's {@code AndroidManifest.xml}. The range of platform versions can be customized using |
| * {@link #setMinCheckedPlatformVersion(int)} and {@link #setMaxCheckedPlatformVersion(int)}. |
| */ |
| public static class Builder { |
| private final File mApkFile; |
| private final DataSource mApkDataSource; |
| private File mV4SignatureFile; |
| |
| private Integer mMinSdkVersion; |
| private int mMaxSdkVersion = Integer.MAX_VALUE; |
| |
| /** |
| * Constructs a new {@code Builder} for verifying the provided APK file. |
| */ |
| public Builder(File apk) { |
| if (apk == null) { |
| throw new NullPointerException("apk == null"); |
| } |
| mApkFile = apk; |
| mApkDataSource = null; |
| } |
| |
| /** |
| * Constructs a new {@code Builder} for verifying the provided APK. |
| */ |
| public Builder(DataSource apk) { |
| if (apk == null) { |
| throw new NullPointerException("apk == null"); |
| } |
| mApkDataSource = apk; |
| mApkFile = null; |
| } |
| |
| /** |
| * Sets the oldest Android platform version for which the APK is verified. APK verification |
| * will confirm that the APK is expected to install successfully on all known Android |
| * platforms starting from the platform version with the provided API Level. The upper end |
| * of the platform versions range can be modified via |
| * {@link #setMaxCheckedPlatformVersion(int)}. |
| * |
| * <p>This method is useful for overriding the default behavior which checks that the APK |
| * will verify on all platform versions supported by the APK, as specified by |
| * {@code android:minSdkVersion} attributes in the APK's {@code AndroidManifest.xml}. |
| * |
| * @param minSdkVersion API Level of the oldest platform for which to verify the APK |
| * |
| * @see #setMinCheckedPlatformVersion(int) |
| */ |
| public Builder setMinCheckedPlatformVersion(int minSdkVersion) { |
| mMinSdkVersion = minSdkVersion; |
| return this; |
| } |
| |
| /** |
| * Sets the newest Android platform version for which the APK is verified. APK verification |
| * will confirm that the APK is expected to install successfully on all platform versions |
| * supported by the APK up until and including the provided version. The lower end |
| * of the platform versions range can be modified via |
| * {@link #setMinCheckedPlatformVersion(int)}. |
| * |
| * @param maxSdkVersion API Level of the newest platform for which to verify the APK |
| * |
| * @see #setMinCheckedPlatformVersion(int) |
| */ |
| public Builder setMaxCheckedPlatformVersion(int maxSdkVersion) { |
| mMaxSdkVersion = maxSdkVersion; |
| return this; |
| } |
| |
| public Builder setV4SignatureFile(File v4SignatureFile) { |
| mV4SignatureFile = v4SignatureFile; |
| return this; |
| } |
| |
| /** |
| * Returns an {@link ApkVerifier} initialized according to the configuration of this |
| * builder. |
| */ |
| public ApkVerifier build() { |
| return new ApkVerifier( |
| mApkFile, |
| mApkDataSource, |
| mV4SignatureFile, |
| mMinSdkVersion, |
| mMaxSdkVersion); |
| } |
| } |
| } |