| /* |
| * Copyright (C) 2020 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.internal.apk.stamp; |
| |
| import static com.android.apksig.internal.apk.ApkSigningBlockUtils.encodeAsSequenceOfLengthPrefixedPairsOfIntAndLengthPrefixedBytes; |
| import static com.android.apksig.internal.apk.stamp.SourceStampConstants.V1_SOURCE_STAMP_BLOCK_ID; |
| |
| import com.android.apksig.ApkVerifier; |
| import com.android.apksig.apk.ApkFormatException; |
| import com.android.apksig.apk.ApkUtils; |
| import com.android.apksig.internal.apk.ApkSigningBlockUtils; |
| import com.android.apksig.internal.apk.ContentDigestAlgorithm; |
| import com.android.apksig.internal.apk.SignatureInfo; |
| import com.android.apksig.internal.util.Pair; |
| import com.android.apksig.util.DataSource; |
| |
| import java.io.IOException; |
| import java.nio.BufferUnderflowException; |
| import java.nio.ByteBuffer; |
| import java.security.NoSuchAlgorithmException; |
| import java.security.cert.CertificateException; |
| import java.security.cert.CertificateFactory; |
| import java.util.ArrayList; |
| import java.util.Collections; |
| import java.util.Comparator; |
| import java.util.List; |
| import java.util.Map; |
| |
| /** |
| * Source Stamp verifier. |
| * |
| * <p>V1 of the source stamp verifies the stamp signature of at most one signature scheme. |
| */ |
| public abstract class V1SourceStampVerifier { |
| |
| /** Hidden constructor to prevent instantiation. */ |
| private V1SourceStampVerifier() {} |
| |
| /** |
| * Verifies the provided APK's SourceStamp signatures and returns the result of verification. |
| * The APK must be considered verified only if {@link ApkSigningBlockUtils.Result#verified} is |
| * {@code true}. If verification fails, the result will contain errors -- see {@link |
| * ApkSigningBlockUtils.Result#getErrors()}. |
| * |
| * @throws NoSuchAlgorithmException if the APK's signatures cannot be verified because a |
| * required cryptographic algorithm implementation is missing |
| * @throws ApkSigningBlockUtils.SignatureNotFoundException if no SourceStamp signatures are |
| * found |
| * @throws IOException if an I/O error occurs when reading the APK |
| */ |
| public static ApkSigningBlockUtils.Result verify( |
| DataSource apk, |
| ApkUtils.ZipSections zipSections, |
| byte[] sourceStampCertificateDigest, |
| Map<ContentDigestAlgorithm, byte[]> apkContentDigests, |
| int minSdkVersion, |
| int maxSdkVersion) |
| throws IOException, NoSuchAlgorithmException, |
| ApkSigningBlockUtils.SignatureNotFoundException { |
| ApkSigningBlockUtils.Result result = |
| new ApkSigningBlockUtils.Result(ApkSigningBlockUtils.VERSION_SOURCE_STAMP); |
| SignatureInfo signatureInfo = |
| ApkSigningBlockUtils.findSignature( |
| apk, zipSections, V1_SOURCE_STAMP_BLOCK_ID, result); |
| |
| verify( |
| signatureInfo.signatureBlock, |
| sourceStampCertificateDigest, |
| apkContentDigests, |
| minSdkVersion, |
| maxSdkVersion, |
| result); |
| return result; |
| } |
| |
| /** |
| * Verifies the provided APK's SourceStamp signatures and outputs the results into the provided |
| * {@code result}. APK is considered verified only if there are no errors reported in the {@code |
| * result}. See {@link #verify(DataSource, ApkUtils.ZipSections, byte[], Map, int, int)} for |
| * more information about the contract of this method. |
| */ |
| private static void verify( |
| ByteBuffer sourceStampBlock, |
| byte[] sourceStampCertificateDigest, |
| Map<ContentDigestAlgorithm, byte[]> apkContentDigests, |
| int minSdkVersion, |
| int maxSdkVersion, |
| ApkSigningBlockUtils.Result result) |
| throws NoSuchAlgorithmException { |
| ApkSigningBlockUtils.Result.SignerInfo signerInfo = |
| new ApkSigningBlockUtils.Result.SignerInfo(); |
| result.signers.add(signerInfo); |
| try { |
| CertificateFactory certFactory = CertificateFactory.getInstance("X.509"); |
| ByteBuffer sourceStampBlockData = |
| ApkSigningBlockUtils.getLengthPrefixedSlice(sourceStampBlock); |
| byte[] digestBytes = |
| encodeAsSequenceOfLengthPrefixedPairsOfIntAndLengthPrefixedBytes( |
| getApkDigests(apkContentDigests)); |
| SourceStampVerifier.verifyV1SourceStamp( |
| sourceStampBlockData, |
| certFactory, |
| signerInfo, |
| digestBytes, |
| sourceStampCertificateDigest, |
| minSdkVersion, |
| maxSdkVersion); |
| result.verified = !result.containsErrors() && !result.containsWarnings(); |
| } catch (CertificateException e) { |
| throw new IllegalStateException("Failed to obtain X.509 CertificateFactory", e); |
| } catch (ApkFormatException | BufferUnderflowException e) { |
| signerInfo.addWarning(ApkVerifier.Issue.SOURCE_STAMP_MALFORMED_SIGNATURE); |
| } |
| } |
| |
| private static List<Pair<Integer, byte[]>> getApkDigests( |
| Map<ContentDigestAlgorithm, byte[]> apkContentDigests) { |
| List<Pair<Integer, byte[]>> digests = new ArrayList<>(); |
| for (Map.Entry<ContentDigestAlgorithm, byte[]> apkContentDigest : |
| apkContentDigests.entrySet()) { |
| digests.add(Pair.of(apkContentDigest.getKey().getId(), apkContentDigest.getValue())); |
| } |
| Collections.sort(digests, Comparator.comparing(Pair::getFirst)); |
| return digests; |
| } |
| } |