1 /* 2 * Copyright (C) 2016 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package com.android.apksig.internal.apk.v2; 18 19 import com.android.apksig.ApkVerifier.Issue; 20 import com.android.apksig.apk.ApkFormatException; 21 import com.android.apksig.apk.ApkUtils; 22 import com.android.apksig.internal.apk.ApkSigningBlockUtils; 23 import com.android.apksig.internal.apk.ContentDigestAlgorithm; 24 import com.android.apksig.internal.apk.SignatureAlgorithm; 25 import com.android.apksig.internal.apk.SignatureInfo; 26 import com.android.apksig.internal.util.ByteBufferUtils; 27 import com.android.apksig.internal.util.GuaranteedEncodedFormX509Certificate; 28 import com.android.apksig.util.DataSource; 29 30 import java.io.ByteArrayInputStream; 31 import java.io.IOException; 32 import java.nio.BufferUnderflowException; 33 import java.nio.ByteBuffer; 34 import java.security.InvalidAlgorithmParameterException; 35 import java.security.InvalidKeyException; 36 import java.security.KeyFactory; 37 import java.security.NoSuchAlgorithmException; 38 import java.security.PublicKey; 39 import java.security.Signature; 40 import java.security.SignatureException; 41 import java.security.cert.CertificateException; 42 import java.security.cert.CertificateFactory; 43 import java.security.cert.X509Certificate; 44 import java.security.spec.AlgorithmParameterSpec; 45 import java.security.spec.X509EncodedKeySpec; 46 import java.util.ArrayList; 47 import java.util.Arrays; 48 import java.util.HashSet; 49 import java.util.List; 50 import java.util.Map; 51 import java.util.Set; 52 53 /** 54 * APK Signature Scheme v2 verifier. 55 * 56 * <p>APK Signature Scheme v2 is a whole-file signature scheme which aims to protect every single 57 * bit of the APK, as opposed to the JAR Signature Scheme which protects only the names and 58 * uncompressed contents of ZIP entries. 59 * 60 * @see <a href="https://source.android.com/security/apksigning/v2.html">APK Signature Scheme v2</a> 61 */ 62 public abstract class V2SchemeVerifier { 63 64 private static final int APK_SIGNATURE_SCHEME_V2_BLOCK_ID = 0x7109871a; 65 66 /** Hidden constructor to prevent instantiation. */ 67 private V2SchemeVerifier() {} 68 69 /** 70 * Verifies the provided APK's APK Signature Scheme v2 signatures and returns the result of 71 * verification. The APK must be considered verified only if 72 * {@link ApkSigningBlockUtils.Result#verified} is 73 * {@code true}. If verification fails, the result will contain errors -- see 74 * {@link ApkSigningBlockUtils.Result#getErrors()}. 75 * 76 * <p>Verification succeeds iff the APK's APK Signature Scheme v2 signatures are expected to 77 * verify on all Android platform versions in the {@code [minSdkVersion, maxSdkVersion]} range. 78 * If the APK's signature is expected to not verify on any of the specified platform versions, 79 * this method returns a result with one or more errors and whose 80 * {@code Result.verified == false}, or this method throws an exception. 81 * 82 * @throws ApkFormatException if the APK is malformed 83 * @throws NoSuchAlgorithmException if the APK's signatures cannot be verified because a 84 * required cryptographic algorithm implementation is missing 85 * @throws ApkSigningBlockUtils.SignatureNotFoundException if no APK Signature Scheme v2 86 * signatures are found 87 * @throws IOException if an I/O error occurs when reading the APK 88 */ 89 public static ApkSigningBlockUtils.Result verify( 90 DataSource apk, 91 ApkUtils.ZipSections zipSections, 92 Map<Integer, String> supportedApkSigSchemeNames, 93 Set<Integer> foundSigSchemeIds, 94 int minSdkVersion, 95 int maxSdkVersion) 96 throws IOException, ApkFormatException, NoSuchAlgorithmException, 97 ApkSigningBlockUtils.SignatureNotFoundException { 98 ApkSigningBlockUtils.Result result = new ApkSigningBlockUtils.Result( 99 ApkSigningBlockUtils.VERSION_APK_SIGNATURE_SCHEME_V2); 100 SignatureInfo signatureInfo = 101 ApkSigningBlockUtils.findSignature(apk, zipSections, 102 APK_SIGNATURE_SCHEME_V2_BLOCK_ID , result); 103 104 DataSource beforeApkSigningBlock = apk.slice(0, signatureInfo.apkSigningBlockOffset); 105 DataSource centralDir = 106 apk.slice( 107 signatureInfo.centralDirOffset, 108 signatureInfo.eocdOffset - signatureInfo.centralDirOffset); 109 ByteBuffer eocd = signatureInfo.eocd; 110 111 verify(beforeApkSigningBlock, 112 signatureInfo.signatureBlock, 113 centralDir, 114 eocd, 115 supportedApkSigSchemeNames, 116 foundSigSchemeIds, 117 minSdkVersion, 118 maxSdkVersion, 119 result); 120 return result; 121 } 122 123 /** 124 * Verifies the provided APK's v2 signatures and outputs the results into the provided 125 * {@code result}. APK is considered verified only if there are no errors reported in the 126 * {@code result}. See {@link #verify(DataSource, ApkUtils.ZipSections, Map, Set, int, int)} for 127 * more information about the contract of this method. 128 * 129 * @param result result populated by this method with interesting information about the APK, 130 * such as information about signers, and verification errors and warnings. 131 */ 132 private static void verify( 133 DataSource beforeApkSigningBlock, 134 ByteBuffer apkSignatureSchemeV2Block, 135 DataSource centralDir, 136 ByteBuffer eocd, 137 Map<Integer, String> supportedApkSigSchemeNames, 138 Set<Integer> foundSigSchemeIds, 139 int minSdkVersion, 140 int maxSdkVersion, 141 ApkSigningBlockUtils.Result result) 142 throws IOException, NoSuchAlgorithmException { 143 Set<ContentDigestAlgorithm> contentDigestsToVerify = new HashSet<>(1); 144 parseSigners( 145 apkSignatureSchemeV2Block, 146 contentDigestsToVerify, 147 supportedApkSigSchemeNames, 148 foundSigSchemeIds, 149 minSdkVersion, 150 maxSdkVersion, 151 result); 152 if (result.containsErrors()) { 153 return; 154 } 155 ApkSigningBlockUtils.verifyIntegrity( 156 beforeApkSigningBlock, centralDir, eocd, contentDigestsToVerify, result); 157 if (!result.containsErrors()) { 158 result.verified = true; 159 } 160 } 161 162 /** 163 * Parses each signer in the provided APK Signature Scheme v2 block and populates corresponding 164 * {@code signerInfos} of the provided {@code result}. 165 * 166 * <p>This verifies signatures over {@code signed-data} block contained in each signer block. 167 * However, this does not verify the integrity of the rest of the APK but rather simply reports 168 * the expected digests of the rest of the APK (see {@code contentDigestsToVerify}). 169 * 170 * <p>This method adds one or more errors to the {@code result} if a verification error is 171 * expected to be encountered on an Android platform version in the 172 * {@code [minSdkVersion, maxSdkVersion]} range. 173 */ 174 private static void parseSigners( 175 ByteBuffer apkSignatureSchemeV2Block, 176 Set<ContentDigestAlgorithm> contentDigestsToVerify, 177 Map<Integer, String> supportedApkSigSchemeNames, 178 Set<Integer> foundApkSigSchemeIds, 179 int minSdkVersion, 180 int maxSdkVersion, 181 ApkSigningBlockUtils.Result result) throws NoSuchAlgorithmException { 182 ByteBuffer signers; 183 try { 184 signers = ApkSigningBlockUtils.getLengthPrefixedSlice(apkSignatureSchemeV2Block); 185 } catch (ApkFormatException e) { 186 result.addError(Issue.V2_SIG_MALFORMED_SIGNERS); 187 return; 188 } 189 if (!signers.hasRemaining()) { 190 result.addError(Issue.V2_SIG_NO_SIGNERS); 191 return; 192 } 193 194 CertificateFactory certFactory; 195 try { 196 certFactory = CertificateFactory.getInstance("X.509"); 197 } catch (CertificateException e) { 198 throw new RuntimeException("Failed to obtain X.509 CertificateFactory", e); 199 } 200 int signerCount = 0; 201 while (signers.hasRemaining()) { 202 int signerIndex = signerCount; 203 signerCount++; 204 ApkSigningBlockUtils.Result.SignerInfo signerInfo = 205 new ApkSigningBlockUtils.Result.SignerInfo(); 206 signerInfo.index = signerIndex; 207 result.signers.add(signerInfo); 208 try { 209 ByteBuffer signer = ApkSigningBlockUtils.getLengthPrefixedSlice(signers); 210 parseSigner( 211 signer, 212 certFactory, 213 signerInfo, 214 contentDigestsToVerify, 215 supportedApkSigSchemeNames, 216 foundApkSigSchemeIds, 217 minSdkVersion, 218 maxSdkVersion); 219 } catch (ApkFormatException | BufferUnderflowException e) { 220 signerInfo.addError(Issue.V2_SIG_MALFORMED_SIGNER); 221 return; 222 } 223 } 224 } 225 226 /** 227 * Parses the provided signer block and populates the {@code result}. 228 * 229 * <p>This verifies signatures over {@code signed-data} contained in this block but does not 230 * verify the integrity of the rest of the APK. To facilitate APK integrity verification, this 231 * method adds the {@code contentDigestsToVerify}. These digests can then be used to verify the 232 * integrity of the APK. 233 * 234 * <p>This method adds one or more errors to the {@code result} if a verification error is 235 * expected to be encountered on an Android platform version in the 236 * {@code [minSdkVersion, maxSdkVersion]} range. 237 */ 238 private static void parseSigner( 239 ByteBuffer signerBlock, 240 CertificateFactory certFactory, 241 ApkSigningBlockUtils.Result.SignerInfo result, 242 Set<ContentDigestAlgorithm> contentDigestsToVerify, 243 Map<Integer, String> supportedApkSigSchemeNames, 244 Set<Integer> foundApkSigSchemeIds, 245 int minSdkVersion, 246 int maxSdkVersion) 247 throws ApkFormatException, NoSuchAlgorithmException { 248 ByteBuffer signedData = ApkSigningBlockUtils.getLengthPrefixedSlice(signerBlock); 249 byte[] signedDataBytes = new byte[signedData.remaining()]; 250 signedData.get(signedDataBytes); 251 signedData.flip(); 252 result.signedData = signedDataBytes; 253 254 ByteBuffer signatures = ApkSigningBlockUtils.getLengthPrefixedSlice(signerBlock); 255 byte[] publicKeyBytes = ApkSigningBlockUtils.readLengthPrefixedByteArray(signerBlock); 256 257 // Parse the signatures block and identify supported signatures 258 int signatureCount = 0; 259 List<ApkSigningBlockUtils.SupportedSignature> supportedSignatures = new ArrayList<>(1); 260 while (signatures.hasRemaining()) { 261 signatureCount++; 262 try { 263 ByteBuffer signature = ApkSigningBlockUtils.getLengthPrefixedSlice(signatures); 264 int sigAlgorithmId = signature.getInt(); 265 byte[] sigBytes = ApkSigningBlockUtils.readLengthPrefixedByteArray(signature); 266 result.signatures.add( 267 new ApkSigningBlockUtils.Result.SignerInfo.Signature( 268 sigAlgorithmId, sigBytes)); 269 SignatureAlgorithm signatureAlgorithm = SignatureAlgorithm.findById(sigAlgorithmId); 270 if (signatureAlgorithm == null) { 271 result.addWarning(Issue.V2_SIG_UNKNOWN_SIG_ALGORITHM, sigAlgorithmId); 272 continue; 273 } 274 supportedSignatures.add( 275 new ApkSigningBlockUtils.SupportedSignature(signatureAlgorithm, sigBytes)); 276 } catch (ApkFormatException | BufferUnderflowException e) { 277 result.addError(Issue.V2_SIG_MALFORMED_SIGNATURE, signatureCount); 278 return; 279 } 280 } 281 if (result.signatures.isEmpty()) { 282 result.addError(Issue.V2_SIG_NO_SIGNATURES); 283 return; 284 } 285 286 // Verify signatures over signed-data block using the public key 287 List<ApkSigningBlockUtils.SupportedSignature> signaturesToVerify = null; 288 try { 289 signaturesToVerify = 290 ApkSigningBlockUtils.getSignaturesToVerify( 291 supportedSignatures, minSdkVersion, maxSdkVersion); 292 } catch (ApkSigningBlockUtils.NoSupportedSignaturesException e) { 293 result.addError(Issue.V2_SIG_NO_SUPPORTED_SIGNATURES); 294 return; 295 } 296 for (ApkSigningBlockUtils.SupportedSignature signature : signaturesToVerify) { 297 SignatureAlgorithm signatureAlgorithm = signature.algorithm; 298 String jcaSignatureAlgorithm = 299 signatureAlgorithm.getJcaSignatureAlgorithmAndParams().getFirst(); 300 AlgorithmParameterSpec jcaSignatureAlgorithmParams = 301 signatureAlgorithm.getJcaSignatureAlgorithmAndParams().getSecond(); 302 String keyAlgorithm = signatureAlgorithm.getJcaKeyAlgorithm(); 303 PublicKey publicKey; 304 try { 305 publicKey = 306 KeyFactory.getInstance(keyAlgorithm).generatePublic( 307 new X509EncodedKeySpec(publicKeyBytes)); 308 } catch (Exception e) { 309 result.addError(Issue.V2_SIG_MALFORMED_PUBLIC_KEY, e); 310 return; 311 } 312 try { 313 Signature sig = Signature.getInstance(jcaSignatureAlgorithm); 314 sig.initVerify(publicKey); 315 if (jcaSignatureAlgorithmParams != null) { 316 sig.setParameter(jcaSignatureAlgorithmParams); 317 } 318 signedData.position(0); 319 sig.update(signedData); 320 byte[] sigBytes = signature.signature; 321 if (!sig.verify(sigBytes)) { 322 result.addError(Issue.V2_SIG_DID_NOT_VERIFY, signatureAlgorithm); 323 return; 324 } 325 result.verifiedSignatures.put(signatureAlgorithm, sigBytes); 326 contentDigestsToVerify.add(signatureAlgorithm.getContentDigestAlgorithm()); 327 } catch (InvalidKeyException | InvalidAlgorithmParameterException 328 | SignatureException e) { 329 result.addError(Issue.V2_SIG_VERIFY_EXCEPTION, signatureAlgorithm, e); 330 return; 331 } 332 } 333 334 // At least one signature over signedData has verified. We can now parse signed-data. 335 signedData.position(0); 336 ByteBuffer digests = ApkSigningBlockUtils.getLengthPrefixedSlice(signedData); 337 ByteBuffer certificates = ApkSigningBlockUtils.getLengthPrefixedSlice(signedData); 338 ByteBuffer additionalAttributes = ApkSigningBlockUtils.getLengthPrefixedSlice(signedData); 339 340 // Parse the certificates block 341 int certificateIndex = -1; 342 while (certificates.hasRemaining()) { 343 certificateIndex++; 344 byte[] encodedCert = ApkSigningBlockUtils.readLengthPrefixedByteArray(certificates); 345 X509Certificate certificate; 346 try { 347 certificate = 348 (X509Certificate) 349 certFactory.generateCertificate( 350 new ByteArrayInputStream(encodedCert)); 351 } catch (CertificateException e) { 352 result.addError( 353 Issue.V2_SIG_MALFORMED_CERTIFICATE, 354 certificateIndex, 355 certificateIndex + 1, 356 e); 357 return; 358 } 359 // Wrap the cert so that the result's getEncoded returns exactly the original encoded 360 // form. Without this, getEncoded may return a different form from what was stored in 361 // the signature. This is because some X509Certificate(Factory) implementations 362 // re-encode certificates. 363 certificate = new GuaranteedEncodedFormX509Certificate(certificate, encodedCert); 364 result.certs.add(certificate); 365 } 366 367 if (result.certs.isEmpty()) { 368 result.addError(Issue.V2_SIG_NO_CERTIFICATES); 369 return; 370 } 371 X509Certificate mainCertificate = result.certs.get(0); 372 byte[] certificatePublicKeyBytes = mainCertificate.getPublicKey().getEncoded(); 373 if (!Arrays.equals(publicKeyBytes, certificatePublicKeyBytes)) { 374 result.addError( 375 Issue.V2_SIG_PUBLIC_KEY_MISMATCH_BETWEEN_CERTIFICATE_AND_SIGNATURES_RECORD, 376 ApkSigningBlockUtils.toHex(certificatePublicKeyBytes), 377 ApkSigningBlockUtils.toHex(publicKeyBytes)); 378 return; 379 } 380 381 // Parse the digests block 382 int digestCount = 0; 383 while (digests.hasRemaining()) { 384 digestCount++; 385 try { 386 ByteBuffer digest = ApkSigningBlockUtils.getLengthPrefixedSlice(digests); 387 int sigAlgorithmId = digest.getInt(); 388 byte[] digestBytes = ApkSigningBlockUtils.readLengthPrefixedByteArray(digest); 389 result.contentDigests.add( 390 new ApkSigningBlockUtils.Result.SignerInfo.ContentDigest( 391 sigAlgorithmId, digestBytes)); 392 } catch (ApkFormatException | BufferUnderflowException e) { 393 result.addError(Issue.V2_SIG_MALFORMED_DIGEST, digestCount); 394 return; 395 } 396 } 397 398 List<Integer> sigAlgsFromSignaturesRecord = new ArrayList<>(result.signatures.size()); 399 for (ApkSigningBlockUtils.Result.SignerInfo.Signature signature : result.signatures) { 400 sigAlgsFromSignaturesRecord.add(signature.getAlgorithmId()); 401 } 402 List<Integer> sigAlgsFromDigestsRecord = new ArrayList<>(result.contentDigests.size()); 403 for (ApkSigningBlockUtils.Result.SignerInfo.ContentDigest digest : result.contentDigests) { 404 sigAlgsFromDigestsRecord.add(digest.getSignatureAlgorithmId()); 405 } 406 407 if (!sigAlgsFromSignaturesRecord.equals(sigAlgsFromDigestsRecord)) { 408 result.addError( 409 Issue.V2_SIG_SIG_ALG_MISMATCH_BETWEEN_SIGNATURES_AND_DIGESTS_RECORDS, 410 sigAlgsFromSignaturesRecord, 411 sigAlgsFromDigestsRecord); 412 return; 413 } 414 415 // Parse the additional attributes block. 416 int additionalAttributeCount = 0; 417 Set<Integer> supportedApkSigSchemeIds = supportedApkSigSchemeNames.keySet(); 418 Set<Integer> supportedExpectedApkSigSchemeIds = new HashSet<>(1); 419 while (additionalAttributes.hasRemaining()) { 420 additionalAttributeCount++; 421 try { 422 ByteBuffer attribute = 423 ApkSigningBlockUtils.getLengthPrefixedSlice(additionalAttributes); 424 int id = attribute.getInt(); 425 byte[] value = ByteBufferUtils.toByteArray(attribute); 426 result.additionalAttributes.add( 427 new ApkSigningBlockUtils.Result.SignerInfo.AdditionalAttribute(id, value)); 428 switch (id) { 429 case V2SchemeSigner.STRIPPING_PROTECTION_ATTR_ID: 430 // stripping protection added when signing with a newer scheme 431 int foundId = attribute.getInt(); 432 if (supportedApkSigSchemeIds.contains(foundId)) { 433 supportedExpectedApkSigSchemeIds.add(id); 434 } else { 435 result.addWarning( 436 Issue.V2_SIG_UNKNOWN_APK_SIG_SCHEME_ID, result.index, foundId); 437 } 438 break; 439 default: 440 result.addWarning(Issue.V2_SIG_UNKNOWN_ADDITIONAL_ATTRIBUTE, id); 441 } 442 } catch (ApkFormatException | BufferUnderflowException e) { 443 result.addError( 444 Issue.V2_SIG_MALFORMED_ADDITIONAL_ATTRIBUTE, additionalAttributeCount); 445 return; 446 } 447 } 448 449 // make sure that all known IDs indicated in stripping protection have already verified 450 for (int id : supportedExpectedApkSigSchemeIds) { 451 if (!foundApkSigSchemeIds.contains(id)) { 452 String apkSigSchemeName = supportedApkSigSchemeNames.get(id); 453 result.addError( 454 Issue.V2_SIG_MISSING_APK_SIG_REFERENCED, 455 result.index, 456 apkSigSchemeName); 457 } 458 } 459 } 460 } 461