18a7c1606d88873c5a1b5764c16cb046b6f2275b2Przemyslaw Szczepaniak/* 28a7c1606d88873c5a1b5764c16cb046b6f2275b2Przemyslaw Szczepaniak * Licensed to the Apache Software Foundation (ASF) under one or more 38a7c1606d88873c5a1b5764c16cb046b6f2275b2Przemyslaw Szczepaniak * contributor license agreements. See the NOTICE file distributed with 48a7c1606d88873c5a1b5764c16cb046b6f2275b2Przemyslaw Szczepaniak * this work for additional information regarding copyright ownership. 58a7c1606d88873c5a1b5764c16cb046b6f2275b2Przemyslaw Szczepaniak * The ASF licenses this file to You under the Apache License, Version 2.0 68a7c1606d88873c5a1b5764c16cb046b6f2275b2Przemyslaw Szczepaniak * (the "License"); you may not use this file except in compliance with 78a7c1606d88873c5a1b5764c16cb046b6f2275b2Przemyslaw Szczepaniak * the License. You may obtain a copy of the License at 88a7c1606d88873c5a1b5764c16cb046b6f2275b2Przemyslaw Szczepaniak * 98a7c1606d88873c5a1b5764c16cb046b6f2275b2Przemyslaw Szczepaniak * http://www.apache.org/licenses/LICENSE-2.0 108a7c1606d88873c5a1b5764c16cb046b6f2275b2Przemyslaw Szczepaniak * 118a7c1606d88873c5a1b5764c16cb046b6f2275b2Przemyslaw Szczepaniak * Unless required by applicable law or agreed to in writing, software 128a7c1606d88873c5a1b5764c16cb046b6f2275b2Przemyslaw Szczepaniak * distributed under the License is distributed on an "AS IS" BASIS, 138a7c1606d88873c5a1b5764c16cb046b6f2275b2Przemyslaw Szczepaniak * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 148a7c1606d88873c5a1b5764c16cb046b6f2275b2Przemyslaw Szczepaniak * See the License for the specific language governing permissions and 158a7c1606d88873c5a1b5764c16cb046b6f2275b2Przemyslaw Szczepaniak * limitations under the License. 168a7c1606d88873c5a1b5764c16cb046b6f2275b2Przemyslaw Szczepaniak */ 178a7c1606d88873c5a1b5764c16cb046b6f2275b2Przemyslaw Szczepaniak 188a7c1606d88873c5a1b5764c16cb046b6f2275b2Przemyslaw Szczepaniakpackage android.util.jar; 198a7c1606d88873c5a1b5764c16cb046b6f2275b2Przemyslaw Szczepaniak 209f00d71787774bf79d4b398c28630f670765b791Tobias Thiererimport android.util.apk.ApkSignatureSchemeV2Verifier; 2167096e08a72beea85979a3aa9fc5b376b2c2b5adDaniel Cashmanimport android.util.apk.ApkSignatureSchemeV3Verifier; 2267096e08a72beea85979a3aa9fc5b376b2c2b5adDaniel Cashman 238a7c1606d88873c5a1b5764c16cb046b6f2275b2Przemyslaw Szczepaniakimport java.io.IOException; 248a7c1606d88873c5a1b5764c16cb046b6f2275b2Przemyslaw Szczepaniakimport java.io.OutputStream; 258a7c1606d88873c5a1b5764c16cb046b6f2275b2Przemyslaw Szczepaniakimport java.nio.charset.StandardCharsets; 268a7c1606d88873c5a1b5764c16cb046b6f2275b2Przemyslaw Szczepaniakimport java.security.GeneralSecurityException; 278a7c1606d88873c5a1b5764c16cb046b6f2275b2Przemyslaw Szczepaniakimport java.security.MessageDigest; 288a7c1606d88873c5a1b5764c16cb046b6f2275b2Przemyslaw Szczepaniakimport java.security.NoSuchAlgorithmException; 298a7c1606d88873c5a1b5764c16cb046b6f2275b2Przemyslaw Szczepaniakimport java.security.cert.Certificate; 308a7c1606d88873c5a1b5764c16cb046b6f2275b2Przemyslaw Szczepaniakimport java.security.cert.X509Certificate; 318a7c1606d88873c5a1b5764c16cb046b6f2275b2Przemyslaw Szczepaniakimport java.util.ArrayList; 328a7c1606d88873c5a1b5764c16cb046b6f2275b2Przemyslaw Szczepaniakimport java.util.HashMap; 338a7c1606d88873c5a1b5764c16cb046b6f2275b2Przemyslaw Szczepaniakimport java.util.Hashtable; 348a7c1606d88873c5a1b5764c16cb046b6f2275b2Przemyslaw Szczepaniakimport java.util.Iterator; 3529045203f3f694cddea7b3f115ea82f648ba0cbaAlex Klyubinimport java.util.List; 368a7c1606d88873c5a1b5764c16cb046b6f2275b2Przemyslaw Szczepaniakimport java.util.Locale; 378a7c1606d88873c5a1b5764c16cb046b6f2275b2Przemyslaw Szczepaniakimport java.util.Map; 38e415718502897a4e5385af47d3bbe8c8257c2e5dAlex Klyubinimport java.util.StringTokenizer; 398a7c1606d88873c5a1b5764c16cb046b6f2275b2Przemyslaw Szczepaniakimport java.util.jar.Attributes; 408a7c1606d88873c5a1b5764c16cb046b6f2275b2Przemyslaw Szczepaniakimport java.util.jar.JarFile; 4167096e08a72beea85979a3aa9fc5b376b2c2b5adDaniel Cashman 428a7c1606d88873c5a1b5764c16cb046b6f2275b2Przemyslaw Szczepaniakimport sun.security.jca.Providers; 438a7c1606d88873c5a1b5764c16cb046b6f2275b2Przemyslaw Szczepaniakimport sun.security.pkcs.PKCS7; 4429045203f3f694cddea7b3f115ea82f648ba0cbaAlex Klyubinimport sun.security.pkcs.SignerInfo; 458a7c1606d88873c5a1b5764c16cb046b6f2275b2Przemyslaw Szczepaniak 468a7c1606d88873c5a1b5764c16cb046b6f2275b2Przemyslaw Szczepaniak/** 478a7c1606d88873c5a1b5764c16cb046b6f2275b2Przemyslaw Szczepaniak * Non-public class used by {@link JarFile} and {@link JarInputStream} to manage 488a7c1606d88873c5a1b5764c16cb046b6f2275b2Przemyslaw Szczepaniak * the verification of signed JARs. {@code JarFile} and {@code JarInputStream} 498a7c1606d88873c5a1b5764c16cb046b6f2275b2Przemyslaw Szczepaniak * objects are expected to have a {@code JarVerifier} instance member which 508a7c1606d88873c5a1b5764c16cb046b6f2275b2Przemyslaw Szczepaniak * can be used to carry out the tasks associated with verifying a signed JAR. 518a7c1606d88873c5a1b5764c16cb046b6f2275b2Przemyslaw Szczepaniak * These tasks would typically include: 528a7c1606d88873c5a1b5764c16cb046b6f2275b2Przemyslaw Szczepaniak * <ul> 538a7c1606d88873c5a1b5764c16cb046b6f2275b2Przemyslaw Szczepaniak * <li>verification of all signed signature files 548a7c1606d88873c5a1b5764c16cb046b6f2275b2Przemyslaw Szczepaniak * <li>confirmation that all signed data was signed only by the party or parties 558a7c1606d88873c5a1b5764c16cb046b6f2275b2Przemyslaw Szczepaniak * specified in the signature block data 568a7c1606d88873c5a1b5764c16cb046b6f2275b2Przemyslaw Szczepaniak * <li>verification that the contents of all signature files (i.e. {@code .SF} 578a7c1606d88873c5a1b5764c16cb046b6f2275b2Przemyslaw Szczepaniak * files) agree with the JAR entries information found in the JAR manifest. 588a7c1606d88873c5a1b5764c16cb046b6f2275b2Przemyslaw Szczepaniak * </ul> 598a7c1606d88873c5a1b5764c16cb046b6f2275b2Przemyslaw Szczepaniak */ 608a7c1606d88873c5a1b5764c16cb046b6f2275b2Przemyslaw Szczepaniakclass StrictJarVerifier { 618a7c1606d88873c5a1b5764c16cb046b6f2275b2Przemyslaw Szczepaniak /** 6267096e08a72beea85979a3aa9fc5b376b2c2b5adDaniel Cashman * {@code .SF} file header section attribute indicating that the APK is signed not just with 6367096e08a72beea85979a3aa9fc5b376b2c2b5adDaniel Cashman * JAR signature scheme but also with APK Signature Scheme v2 or newer. This attribute 6467096e08a72beea85979a3aa9fc5b376b2c2b5adDaniel Cashman * facilitates v2 signature stripping detection. 6567096e08a72beea85979a3aa9fc5b376b2c2b5adDaniel Cashman * 6667096e08a72beea85979a3aa9fc5b376b2c2b5adDaniel Cashman * <p>The attribute contains a comma-separated set of signature scheme IDs. 6767096e08a72beea85979a3aa9fc5b376b2c2b5adDaniel Cashman */ 6867096e08a72beea85979a3aa9fc5b376b2c2b5adDaniel Cashman private static final String SF_ATTRIBUTE_ANDROID_APK_SIGNED_NAME = "X-Android-APK-Signed"; 6967096e08a72beea85979a3aa9fc5b376b2c2b5adDaniel Cashman 7067096e08a72beea85979a3aa9fc5b376b2c2b5adDaniel Cashman /** 718a7c1606d88873c5a1b5764c16cb046b6f2275b2Przemyslaw Szczepaniak * List of accepted digest algorithms. This list is in order from most 728a7c1606d88873c5a1b5764c16cb046b6f2275b2Przemyslaw Szczepaniak * preferred to least preferred. 738a7c1606d88873c5a1b5764c16cb046b6f2275b2Przemyslaw Szczepaniak */ 748a7c1606d88873c5a1b5764c16cb046b6f2275b2Przemyslaw Szczepaniak private static final String[] DIGEST_ALGORITHMS = new String[] { 758a7c1606d88873c5a1b5764c16cb046b6f2275b2Przemyslaw Szczepaniak "SHA-512", 768a7c1606d88873c5a1b5764c16cb046b6f2275b2Przemyslaw Szczepaniak "SHA-384", 778a7c1606d88873c5a1b5764c16cb046b6f2275b2Przemyslaw Szczepaniak "SHA-256", 788a7c1606d88873c5a1b5764c16cb046b6f2275b2Przemyslaw Szczepaniak "SHA1", 798a7c1606d88873c5a1b5764c16cb046b6f2275b2Przemyslaw Szczepaniak }; 808a7c1606d88873c5a1b5764c16cb046b6f2275b2Przemyslaw Szczepaniak 818a7c1606d88873c5a1b5764c16cb046b6f2275b2Przemyslaw Szczepaniak private final String jarName; 828a7c1606d88873c5a1b5764c16cb046b6f2275b2Przemyslaw Szczepaniak private final StrictJarManifest manifest; 838a7c1606d88873c5a1b5764c16cb046b6f2275b2Przemyslaw Szczepaniak private final HashMap<String, byte[]> metaEntries; 848a7c1606d88873c5a1b5764c16cb046b6f2275b2Przemyslaw Szczepaniak private final int mainAttributesEnd; 859b59bc459b4cb5415909641bd1e981100bfafb2bAlex Klyubin private final boolean signatureSchemeRollbackProtectionsEnforced; 868a7c1606d88873c5a1b5764c16cb046b6f2275b2Przemyslaw Szczepaniak 878a7c1606d88873c5a1b5764c16cb046b6f2275b2Przemyslaw Szczepaniak private final Hashtable<String, HashMap<String, Attributes>> signatures = 888a7c1606d88873c5a1b5764c16cb046b6f2275b2Przemyslaw Szczepaniak new Hashtable<String, HashMap<String, Attributes>>(5); 898a7c1606d88873c5a1b5764c16cb046b6f2275b2Przemyslaw Szczepaniak 908a7c1606d88873c5a1b5764c16cb046b6f2275b2Przemyslaw Szczepaniak private final Hashtable<String, Certificate[]> certificates = 918a7c1606d88873c5a1b5764c16cb046b6f2275b2Przemyslaw Szczepaniak new Hashtable<String, Certificate[]>(5); 928a7c1606d88873c5a1b5764c16cb046b6f2275b2Przemyslaw Szczepaniak 938a7c1606d88873c5a1b5764c16cb046b6f2275b2Przemyslaw Szczepaniak private final Hashtable<String, Certificate[][]> verifiedEntries = 948a7c1606d88873c5a1b5764c16cb046b6f2275b2Przemyslaw Szczepaniak new Hashtable<String, Certificate[][]>(); 958a7c1606d88873c5a1b5764c16cb046b6f2275b2Przemyslaw Szczepaniak 968a7c1606d88873c5a1b5764c16cb046b6f2275b2Przemyslaw Szczepaniak /** 978a7c1606d88873c5a1b5764c16cb046b6f2275b2Przemyslaw Szczepaniak * Stores and a hash and a message digest and verifies that massage digest 988a7c1606d88873c5a1b5764c16cb046b6f2275b2Przemyslaw Szczepaniak * matches the hash. 998a7c1606d88873c5a1b5764c16cb046b6f2275b2Przemyslaw Szczepaniak */ 1008a7c1606d88873c5a1b5764c16cb046b6f2275b2Przemyslaw Szczepaniak static class VerifierEntry extends OutputStream { 1018a7c1606d88873c5a1b5764c16cb046b6f2275b2Przemyslaw Szczepaniak 1028a7c1606d88873c5a1b5764c16cb046b6f2275b2Przemyslaw Szczepaniak private final String name; 1038a7c1606d88873c5a1b5764c16cb046b6f2275b2Przemyslaw Szczepaniak 1048a7c1606d88873c5a1b5764c16cb046b6f2275b2Przemyslaw Szczepaniak private final MessageDigest digest; 1058a7c1606d88873c5a1b5764c16cb046b6f2275b2Przemyslaw Szczepaniak 1068a7c1606d88873c5a1b5764c16cb046b6f2275b2Przemyslaw Szczepaniak private final byte[] hash; 1078a7c1606d88873c5a1b5764c16cb046b6f2275b2Przemyslaw Szczepaniak 1088a7c1606d88873c5a1b5764c16cb046b6f2275b2Przemyslaw Szczepaniak private final Certificate[][] certChains; 1098a7c1606d88873c5a1b5764c16cb046b6f2275b2Przemyslaw Szczepaniak 1108a7c1606d88873c5a1b5764c16cb046b6f2275b2Przemyslaw Szczepaniak private final Hashtable<String, Certificate[][]> verifiedEntries; 1118a7c1606d88873c5a1b5764c16cb046b6f2275b2Przemyslaw Szczepaniak 1128a7c1606d88873c5a1b5764c16cb046b6f2275b2Przemyslaw Szczepaniak VerifierEntry(String name, MessageDigest digest, byte[] hash, 1138a7c1606d88873c5a1b5764c16cb046b6f2275b2Przemyslaw Szczepaniak Certificate[][] certChains, Hashtable<String, Certificate[][]> verifedEntries) { 1148a7c1606d88873c5a1b5764c16cb046b6f2275b2Przemyslaw Szczepaniak this.name = name; 1158a7c1606d88873c5a1b5764c16cb046b6f2275b2Przemyslaw Szczepaniak this.digest = digest; 1168a7c1606d88873c5a1b5764c16cb046b6f2275b2Przemyslaw Szczepaniak this.hash = hash; 1178a7c1606d88873c5a1b5764c16cb046b6f2275b2Przemyslaw Szczepaniak this.certChains = certChains; 1188a7c1606d88873c5a1b5764c16cb046b6f2275b2Przemyslaw Szczepaniak this.verifiedEntries = verifedEntries; 1198a7c1606d88873c5a1b5764c16cb046b6f2275b2Przemyslaw Szczepaniak } 1208a7c1606d88873c5a1b5764c16cb046b6f2275b2Przemyslaw Szczepaniak 1218a7c1606d88873c5a1b5764c16cb046b6f2275b2Przemyslaw Szczepaniak /** 1228a7c1606d88873c5a1b5764c16cb046b6f2275b2Przemyslaw Szczepaniak * Updates a digest with one byte. 1238a7c1606d88873c5a1b5764c16cb046b6f2275b2Przemyslaw Szczepaniak */ 1248a7c1606d88873c5a1b5764c16cb046b6f2275b2Przemyslaw Szczepaniak @Override 1258a7c1606d88873c5a1b5764c16cb046b6f2275b2Przemyslaw Szczepaniak public void write(int value) { 1268a7c1606d88873c5a1b5764c16cb046b6f2275b2Przemyslaw Szczepaniak digest.update((byte) value); 1278a7c1606d88873c5a1b5764c16cb046b6f2275b2Przemyslaw Szczepaniak } 1288a7c1606d88873c5a1b5764c16cb046b6f2275b2Przemyslaw Szczepaniak 1298a7c1606d88873c5a1b5764c16cb046b6f2275b2Przemyslaw Szczepaniak /** 1308a7c1606d88873c5a1b5764c16cb046b6f2275b2Przemyslaw Szczepaniak * Updates a digest with byte array. 1318a7c1606d88873c5a1b5764c16cb046b6f2275b2Przemyslaw Szczepaniak */ 1328a7c1606d88873c5a1b5764c16cb046b6f2275b2Przemyslaw Szczepaniak @Override 1338a7c1606d88873c5a1b5764c16cb046b6f2275b2Przemyslaw Szczepaniak public void write(byte[] buf, int off, int nbytes) { 1348a7c1606d88873c5a1b5764c16cb046b6f2275b2Przemyslaw Szczepaniak digest.update(buf, off, nbytes); 1358a7c1606d88873c5a1b5764c16cb046b6f2275b2Przemyslaw Szczepaniak } 1368a7c1606d88873c5a1b5764c16cb046b6f2275b2Przemyslaw Szczepaniak 1378a7c1606d88873c5a1b5764c16cb046b6f2275b2Przemyslaw Szczepaniak /** 1388a7c1606d88873c5a1b5764c16cb046b6f2275b2Przemyslaw Szczepaniak * Verifies that the digests stored in the manifest match the decrypted 1398a7c1606d88873c5a1b5764c16cb046b6f2275b2Przemyslaw Szczepaniak * digests from the .SF file. This indicates the validity of the 1408a7c1606d88873c5a1b5764c16cb046b6f2275b2Przemyslaw Szczepaniak * signing, not the integrity of the file, as its digest must be 1418a7c1606d88873c5a1b5764c16cb046b6f2275b2Przemyslaw Szczepaniak * calculated and verified when its contents are read. 1428a7c1606d88873c5a1b5764c16cb046b6f2275b2Przemyslaw Szczepaniak * 1438a7c1606d88873c5a1b5764c16cb046b6f2275b2Przemyslaw Szczepaniak * @throws SecurityException 1448a7c1606d88873c5a1b5764c16cb046b6f2275b2Przemyslaw Szczepaniak * if the digest value stored in the manifest does <i>not</i> 1458a7c1606d88873c5a1b5764c16cb046b6f2275b2Przemyslaw Szczepaniak * agree with the decrypted digest as recovered from the 1468a7c1606d88873c5a1b5764c16cb046b6f2275b2Przemyslaw Szczepaniak * <code>.SF</code> file. 1478a7c1606d88873c5a1b5764c16cb046b6f2275b2Przemyslaw Szczepaniak */ 1488a7c1606d88873c5a1b5764c16cb046b6f2275b2Przemyslaw Szczepaniak void verify() { 1498a7c1606d88873c5a1b5764c16cb046b6f2275b2Przemyslaw Szczepaniak byte[] d = digest.digest(); 1509f00d71787774bf79d4b398c28630f670765b791Tobias Thierer if (!verifyMessageDigest(d, hash)) { 1518a7c1606d88873c5a1b5764c16cb046b6f2275b2Przemyslaw Szczepaniak throw invalidDigest(JarFile.MANIFEST_NAME, name, name); 1528a7c1606d88873c5a1b5764c16cb046b6f2275b2Przemyslaw Szczepaniak } 1538a7c1606d88873c5a1b5764c16cb046b6f2275b2Przemyslaw Szczepaniak verifiedEntries.put(name, certChains); 1548a7c1606d88873c5a1b5764c16cb046b6f2275b2Przemyslaw Szczepaniak } 1558a7c1606d88873c5a1b5764c16cb046b6f2275b2Przemyslaw Szczepaniak } 1568a7c1606d88873c5a1b5764c16cb046b6f2275b2Przemyslaw Szczepaniak 1578a7c1606d88873c5a1b5764c16cb046b6f2275b2Przemyslaw Szczepaniak private static SecurityException invalidDigest(String signatureFile, String name, 1588a7c1606d88873c5a1b5764c16cb046b6f2275b2Przemyslaw Szczepaniak String jarName) { 1598a7c1606d88873c5a1b5764c16cb046b6f2275b2Przemyslaw Szczepaniak throw new SecurityException(signatureFile + " has invalid digest for " + name + 1608a7c1606d88873c5a1b5764c16cb046b6f2275b2Przemyslaw Szczepaniak " in " + jarName); 1618a7c1606d88873c5a1b5764c16cb046b6f2275b2Przemyslaw Szczepaniak } 1628a7c1606d88873c5a1b5764c16cb046b6f2275b2Przemyslaw Szczepaniak 1638a7c1606d88873c5a1b5764c16cb046b6f2275b2Przemyslaw Szczepaniak private static SecurityException failedVerification(String jarName, String signatureFile) { 1648a7c1606d88873c5a1b5764c16cb046b6f2275b2Przemyslaw Szczepaniak throw new SecurityException(jarName + " failed verification of " + signatureFile); 1658a7c1606d88873c5a1b5764c16cb046b6f2275b2Przemyslaw Szczepaniak } 1668a7c1606d88873c5a1b5764c16cb046b6f2275b2Przemyslaw Szczepaniak 1678a7c1606d88873c5a1b5764c16cb046b6f2275b2Przemyslaw Szczepaniak private static SecurityException failedVerification(String jarName, String signatureFile, 1688a7c1606d88873c5a1b5764c16cb046b6f2275b2Przemyslaw Szczepaniak Throwable e) { 1698a7c1606d88873c5a1b5764c16cb046b6f2275b2Przemyslaw Szczepaniak throw new SecurityException(jarName + " failed verification of " + signatureFile, e); 1708a7c1606d88873c5a1b5764c16cb046b6f2275b2Przemyslaw Szczepaniak } 1718a7c1606d88873c5a1b5764c16cb046b6f2275b2Przemyslaw Szczepaniak 1728a7c1606d88873c5a1b5764c16cb046b6f2275b2Przemyslaw Szczepaniak 1738a7c1606d88873c5a1b5764c16cb046b6f2275b2Przemyslaw Szczepaniak /** 1748a7c1606d88873c5a1b5764c16cb046b6f2275b2Przemyslaw Szczepaniak * Constructs and returns a new instance of {@code JarVerifier}. 1758a7c1606d88873c5a1b5764c16cb046b6f2275b2Przemyslaw Szczepaniak * 1768a7c1606d88873c5a1b5764c16cb046b6f2275b2Przemyslaw Szczepaniak * @param name 1778a7c1606d88873c5a1b5764c16cb046b6f2275b2Przemyslaw Szczepaniak * the name of the JAR file being verified. 1789b59bc459b4cb5415909641bd1e981100bfafb2bAlex Klyubin * 1799b59bc459b4cb5415909641bd1e981100bfafb2bAlex Klyubin * @param signatureSchemeRollbackProtectionsEnforced {@code true} to enforce protections against 1809b59bc459b4cb5415909641bd1e981100bfafb2bAlex Klyubin * stripping newer signature schemes (e.g., APK Signature Scheme v2) from the file, or 1819b59bc459b4cb5415909641bd1e981100bfafb2bAlex Klyubin * {@code false} to ignore any such protections. 1828a7c1606d88873c5a1b5764c16cb046b6f2275b2Przemyslaw Szczepaniak */ 1838a7c1606d88873c5a1b5764c16cb046b6f2275b2Przemyslaw Szczepaniak StrictJarVerifier(String name, StrictJarManifest manifest, 1849b59bc459b4cb5415909641bd1e981100bfafb2bAlex Klyubin HashMap<String, byte[]> metaEntries, boolean signatureSchemeRollbackProtectionsEnforced) { 1858a7c1606d88873c5a1b5764c16cb046b6f2275b2Przemyslaw Szczepaniak jarName = name; 1868a7c1606d88873c5a1b5764c16cb046b6f2275b2Przemyslaw Szczepaniak this.manifest = manifest; 1878a7c1606d88873c5a1b5764c16cb046b6f2275b2Przemyslaw Szczepaniak this.metaEntries = metaEntries; 1888a7c1606d88873c5a1b5764c16cb046b6f2275b2Przemyslaw Szczepaniak this.mainAttributesEnd = manifest.getMainAttributesEnd(); 1899b59bc459b4cb5415909641bd1e981100bfafb2bAlex Klyubin this.signatureSchemeRollbackProtectionsEnforced = 1909b59bc459b4cb5415909641bd1e981100bfafb2bAlex Klyubin signatureSchemeRollbackProtectionsEnforced; 1918a7c1606d88873c5a1b5764c16cb046b6f2275b2Przemyslaw Szczepaniak } 1928a7c1606d88873c5a1b5764c16cb046b6f2275b2Przemyslaw Szczepaniak 1938a7c1606d88873c5a1b5764c16cb046b6f2275b2Przemyslaw Szczepaniak /** 1948a7c1606d88873c5a1b5764c16cb046b6f2275b2Przemyslaw Szczepaniak * Invoked for each new JAR entry read operation from the input 1958a7c1606d88873c5a1b5764c16cb046b6f2275b2Przemyslaw Szczepaniak * stream. This method constructs and returns a new {@link VerifierEntry} 1968a7c1606d88873c5a1b5764c16cb046b6f2275b2Przemyslaw Szczepaniak * which contains the certificates used to sign the entry and its hash value 1978a7c1606d88873c5a1b5764c16cb046b6f2275b2Przemyslaw Szczepaniak * as specified in the JAR MANIFEST format. 1988a7c1606d88873c5a1b5764c16cb046b6f2275b2Przemyslaw Szczepaniak * 1998a7c1606d88873c5a1b5764c16cb046b6f2275b2Przemyslaw Szczepaniak * @param name 2008a7c1606d88873c5a1b5764c16cb046b6f2275b2Przemyslaw Szczepaniak * the name of an entry in a JAR file which is <b>not</b> in the 2018a7c1606d88873c5a1b5764c16cb046b6f2275b2Przemyslaw Szczepaniak * {@code META-INF} directory. 2028a7c1606d88873c5a1b5764c16cb046b6f2275b2Przemyslaw Szczepaniak * @return a new instance of {@link VerifierEntry} which can be used by 2038a7c1606d88873c5a1b5764c16cb046b6f2275b2Przemyslaw Szczepaniak * callers as an {@link OutputStream}. 2048a7c1606d88873c5a1b5764c16cb046b6f2275b2Przemyslaw Szczepaniak */ 2058a7c1606d88873c5a1b5764c16cb046b6f2275b2Przemyslaw Szczepaniak VerifierEntry initEntry(String name) { 2068a7c1606d88873c5a1b5764c16cb046b6f2275b2Przemyslaw Szczepaniak // If no manifest is present by the time an entry is found, 2078a7c1606d88873c5a1b5764c16cb046b6f2275b2Przemyslaw Szczepaniak // verification cannot occur. If no signature files have 2088a7c1606d88873c5a1b5764c16cb046b6f2275b2Przemyslaw Szczepaniak // been found, do not verify. 2098a7c1606d88873c5a1b5764c16cb046b6f2275b2Przemyslaw Szczepaniak if (manifest == null || signatures.isEmpty()) { 2108a7c1606d88873c5a1b5764c16cb046b6f2275b2Przemyslaw Szczepaniak return null; 2118a7c1606d88873c5a1b5764c16cb046b6f2275b2Przemyslaw Szczepaniak } 2128a7c1606d88873c5a1b5764c16cb046b6f2275b2Przemyslaw Szczepaniak 2138a7c1606d88873c5a1b5764c16cb046b6f2275b2Przemyslaw Szczepaniak Attributes attributes = manifest.getAttributes(name); 2148a7c1606d88873c5a1b5764c16cb046b6f2275b2Przemyslaw Szczepaniak // entry has no digest 2158a7c1606d88873c5a1b5764c16cb046b6f2275b2Przemyslaw Szczepaniak if (attributes == null) { 2168a7c1606d88873c5a1b5764c16cb046b6f2275b2Przemyslaw Szczepaniak return null; 2178a7c1606d88873c5a1b5764c16cb046b6f2275b2Przemyslaw Szczepaniak } 2188a7c1606d88873c5a1b5764c16cb046b6f2275b2Przemyslaw Szczepaniak 2198a7c1606d88873c5a1b5764c16cb046b6f2275b2Przemyslaw Szczepaniak ArrayList<Certificate[]> certChains = new ArrayList<Certificate[]>(); 2208a7c1606d88873c5a1b5764c16cb046b6f2275b2Przemyslaw Szczepaniak Iterator<Map.Entry<String, HashMap<String, Attributes>>> it = signatures.entrySet().iterator(); 2218a7c1606d88873c5a1b5764c16cb046b6f2275b2Przemyslaw Szczepaniak while (it.hasNext()) { 2228a7c1606d88873c5a1b5764c16cb046b6f2275b2Przemyslaw Szczepaniak Map.Entry<String, HashMap<String, Attributes>> entry = it.next(); 2238a7c1606d88873c5a1b5764c16cb046b6f2275b2Przemyslaw Szczepaniak HashMap<String, Attributes> hm = entry.getValue(); 2248a7c1606d88873c5a1b5764c16cb046b6f2275b2Przemyslaw Szczepaniak if (hm.get(name) != null) { 2258a7c1606d88873c5a1b5764c16cb046b6f2275b2Przemyslaw Szczepaniak // Found an entry for entry name in .SF file 2268a7c1606d88873c5a1b5764c16cb046b6f2275b2Przemyslaw Szczepaniak String signatureFile = entry.getKey(); 2278a7c1606d88873c5a1b5764c16cb046b6f2275b2Przemyslaw Szczepaniak Certificate[] certChain = certificates.get(signatureFile); 2288a7c1606d88873c5a1b5764c16cb046b6f2275b2Przemyslaw Szczepaniak if (certChain != null) { 2298a7c1606d88873c5a1b5764c16cb046b6f2275b2Przemyslaw Szczepaniak certChains.add(certChain); 2308a7c1606d88873c5a1b5764c16cb046b6f2275b2Przemyslaw Szczepaniak } 2318a7c1606d88873c5a1b5764c16cb046b6f2275b2Przemyslaw Szczepaniak } 2328a7c1606d88873c5a1b5764c16cb046b6f2275b2Przemyslaw Szczepaniak } 2338a7c1606d88873c5a1b5764c16cb046b6f2275b2Przemyslaw Szczepaniak 2348a7c1606d88873c5a1b5764c16cb046b6f2275b2Przemyslaw Szczepaniak // entry is not signed 2358a7c1606d88873c5a1b5764c16cb046b6f2275b2Przemyslaw Szczepaniak if (certChains.isEmpty()) { 2368a7c1606d88873c5a1b5764c16cb046b6f2275b2Przemyslaw Szczepaniak return null; 2378a7c1606d88873c5a1b5764c16cb046b6f2275b2Przemyslaw Szczepaniak } 2388a7c1606d88873c5a1b5764c16cb046b6f2275b2Przemyslaw Szczepaniak Certificate[][] certChainsArray = certChains.toArray(new Certificate[certChains.size()][]); 2398a7c1606d88873c5a1b5764c16cb046b6f2275b2Przemyslaw Szczepaniak 2408a7c1606d88873c5a1b5764c16cb046b6f2275b2Przemyslaw Szczepaniak for (int i = 0; i < DIGEST_ALGORITHMS.length; i++) { 2418a7c1606d88873c5a1b5764c16cb046b6f2275b2Przemyslaw Szczepaniak final String algorithm = DIGEST_ALGORITHMS[i]; 2428a7c1606d88873c5a1b5764c16cb046b6f2275b2Przemyslaw Szczepaniak final String hash = attributes.getValue(algorithm + "-Digest"); 2438a7c1606d88873c5a1b5764c16cb046b6f2275b2Przemyslaw Szczepaniak if (hash == null) { 2448a7c1606d88873c5a1b5764c16cb046b6f2275b2Przemyslaw Szczepaniak continue; 2458a7c1606d88873c5a1b5764c16cb046b6f2275b2Przemyslaw Szczepaniak } 2468a7c1606d88873c5a1b5764c16cb046b6f2275b2Przemyslaw Szczepaniak byte[] hashBytes = hash.getBytes(StandardCharsets.ISO_8859_1); 2478a7c1606d88873c5a1b5764c16cb046b6f2275b2Przemyslaw Szczepaniak 2488a7c1606d88873c5a1b5764c16cb046b6f2275b2Przemyslaw Szczepaniak try { 2498a7c1606d88873c5a1b5764c16cb046b6f2275b2Przemyslaw Szczepaniak return new VerifierEntry(name, MessageDigest.getInstance(algorithm), hashBytes, 2508a7c1606d88873c5a1b5764c16cb046b6f2275b2Przemyslaw Szczepaniak certChainsArray, verifiedEntries); 2518a7c1606d88873c5a1b5764c16cb046b6f2275b2Przemyslaw Szczepaniak } catch (NoSuchAlgorithmException ignored) { 2528a7c1606d88873c5a1b5764c16cb046b6f2275b2Przemyslaw Szczepaniak } 2538a7c1606d88873c5a1b5764c16cb046b6f2275b2Przemyslaw Szczepaniak } 2548a7c1606d88873c5a1b5764c16cb046b6f2275b2Przemyslaw Szczepaniak return null; 2558a7c1606d88873c5a1b5764c16cb046b6f2275b2Przemyslaw Szczepaniak } 2568a7c1606d88873c5a1b5764c16cb046b6f2275b2Przemyslaw Szczepaniak 2578a7c1606d88873c5a1b5764c16cb046b6f2275b2Przemyslaw Szczepaniak /** 2588a7c1606d88873c5a1b5764c16cb046b6f2275b2Przemyslaw Szczepaniak * Add a new meta entry to the internal collection of data held on each JAR 2598a7c1606d88873c5a1b5764c16cb046b6f2275b2Przemyslaw Szczepaniak * entry in the {@code META-INF} directory including the manifest 2608a7c1606d88873c5a1b5764c16cb046b6f2275b2Przemyslaw Szczepaniak * file itself. Files associated with the signing of a JAR would also be 2618a7c1606d88873c5a1b5764c16cb046b6f2275b2Przemyslaw Szczepaniak * added to this collection. 2628a7c1606d88873c5a1b5764c16cb046b6f2275b2Przemyslaw Szczepaniak * 2638a7c1606d88873c5a1b5764c16cb046b6f2275b2Przemyslaw Szczepaniak * @param name 2648a7c1606d88873c5a1b5764c16cb046b6f2275b2Przemyslaw Szczepaniak * the name of the file located in the {@code META-INF} 2658a7c1606d88873c5a1b5764c16cb046b6f2275b2Przemyslaw Szczepaniak * directory. 2668a7c1606d88873c5a1b5764c16cb046b6f2275b2Przemyslaw Szczepaniak * @param buf 2678a7c1606d88873c5a1b5764c16cb046b6f2275b2Przemyslaw Szczepaniak * the file bytes for the file called {@code name}. 2688a7c1606d88873c5a1b5764c16cb046b6f2275b2Przemyslaw Szczepaniak * @see #removeMetaEntries() 2698a7c1606d88873c5a1b5764c16cb046b6f2275b2Przemyslaw Szczepaniak */ 2708a7c1606d88873c5a1b5764c16cb046b6f2275b2Przemyslaw Szczepaniak void addMetaEntry(String name, byte[] buf) { 2718a7c1606d88873c5a1b5764c16cb046b6f2275b2Przemyslaw Szczepaniak metaEntries.put(name.toUpperCase(Locale.US), buf); 2728a7c1606d88873c5a1b5764c16cb046b6f2275b2Przemyslaw Szczepaniak } 2738a7c1606d88873c5a1b5764c16cb046b6f2275b2Przemyslaw Szczepaniak 2748a7c1606d88873c5a1b5764c16cb046b6f2275b2Przemyslaw Szczepaniak /** 2758a7c1606d88873c5a1b5764c16cb046b6f2275b2Przemyslaw Szczepaniak * If the associated JAR file is signed, check on the validity of all of the 2768a7c1606d88873c5a1b5764c16cb046b6f2275b2Przemyslaw Szczepaniak * known signatures. 2778a7c1606d88873c5a1b5764c16cb046b6f2275b2Przemyslaw Szczepaniak * 2788a7c1606d88873c5a1b5764c16cb046b6f2275b2Przemyslaw Szczepaniak * @return {@code true} if the associated JAR is signed and an internal 2798a7c1606d88873c5a1b5764c16cb046b6f2275b2Przemyslaw Szczepaniak * check verifies the validity of the signature(s). {@code false} if 2808a7c1606d88873c5a1b5764c16cb046b6f2275b2Przemyslaw Szczepaniak * the associated JAR file has no entries at all in its {@code 2818a7c1606d88873c5a1b5764c16cb046b6f2275b2Przemyslaw Szczepaniak * META-INF} directory. This situation is indicative of an invalid 2828a7c1606d88873c5a1b5764c16cb046b6f2275b2Przemyslaw Szczepaniak * JAR file. 2838a7c1606d88873c5a1b5764c16cb046b6f2275b2Przemyslaw Szczepaniak * <p> 2848a7c1606d88873c5a1b5764c16cb046b6f2275b2Przemyslaw Szczepaniak * Will also return {@code true} if the JAR file is <i>not</i> 2858a7c1606d88873c5a1b5764c16cb046b6f2275b2Przemyslaw Szczepaniak * signed. 2868a7c1606d88873c5a1b5764c16cb046b6f2275b2Przemyslaw Szczepaniak * @throws SecurityException 2878a7c1606d88873c5a1b5764c16cb046b6f2275b2Przemyslaw Szczepaniak * if the JAR file is signed and it is determined that a 2888a7c1606d88873c5a1b5764c16cb046b6f2275b2Przemyslaw Szczepaniak * signature block file contains an invalid signature for the 2898a7c1606d88873c5a1b5764c16cb046b6f2275b2Przemyslaw Szczepaniak * corresponding signature file. 2908a7c1606d88873c5a1b5764c16cb046b6f2275b2Przemyslaw Szczepaniak */ 2918a7c1606d88873c5a1b5764c16cb046b6f2275b2Przemyslaw Szczepaniak synchronized boolean readCertificates() { 2928a7c1606d88873c5a1b5764c16cb046b6f2275b2Przemyslaw Szczepaniak if (metaEntries.isEmpty()) { 2938a7c1606d88873c5a1b5764c16cb046b6f2275b2Przemyslaw Szczepaniak return false; 2948a7c1606d88873c5a1b5764c16cb046b6f2275b2Przemyslaw Szczepaniak } 2958a7c1606d88873c5a1b5764c16cb046b6f2275b2Przemyslaw Szczepaniak 2968a7c1606d88873c5a1b5764c16cb046b6f2275b2Przemyslaw Szczepaniak Iterator<String> it = metaEntries.keySet().iterator(); 2978a7c1606d88873c5a1b5764c16cb046b6f2275b2Przemyslaw Szczepaniak while (it.hasNext()) { 2988a7c1606d88873c5a1b5764c16cb046b6f2275b2Przemyslaw Szczepaniak String key = it.next(); 2998a7c1606d88873c5a1b5764c16cb046b6f2275b2Przemyslaw Szczepaniak if (key.endsWith(".DSA") || key.endsWith(".RSA") || key.endsWith(".EC")) { 3008a7c1606d88873c5a1b5764c16cb046b6f2275b2Przemyslaw Szczepaniak verifyCertificate(key); 3018a7c1606d88873c5a1b5764c16cb046b6f2275b2Przemyslaw Szczepaniak it.remove(); 3028a7c1606d88873c5a1b5764c16cb046b6f2275b2Przemyslaw Szczepaniak } 3038a7c1606d88873c5a1b5764c16cb046b6f2275b2Przemyslaw Szczepaniak } 3048a7c1606d88873c5a1b5764c16cb046b6f2275b2Przemyslaw Szczepaniak return true; 3058a7c1606d88873c5a1b5764c16cb046b6f2275b2Przemyslaw Szczepaniak } 3068a7c1606d88873c5a1b5764c16cb046b6f2275b2Przemyslaw Szczepaniak 3078a7c1606d88873c5a1b5764c16cb046b6f2275b2Przemyslaw Szczepaniak /** 3088a7c1606d88873c5a1b5764c16cb046b6f2275b2Przemyslaw Szczepaniak * Verifies that the signature computed from {@code sfBytes} matches 3098a7c1606d88873c5a1b5764c16cb046b6f2275b2Przemyslaw Szczepaniak * that specified in {@code blockBytes} (which is a PKCS7 block). Returns 3108a7c1606d88873c5a1b5764c16cb046b6f2275b2Przemyslaw Szczepaniak * certificates listed in the PKCS7 block. Throws a {@code GeneralSecurityException} 3118a7c1606d88873c5a1b5764c16cb046b6f2275b2Przemyslaw Szczepaniak * if something goes wrong during verification. 3128a7c1606d88873c5a1b5764c16cb046b6f2275b2Przemyslaw Szczepaniak */ 3138a7c1606d88873c5a1b5764c16cb046b6f2275b2Przemyslaw Szczepaniak static Certificate[] verifyBytes(byte[] blockBytes, byte[] sfBytes) 3148a7c1606d88873c5a1b5764c16cb046b6f2275b2Przemyslaw Szczepaniak throws GeneralSecurityException { 3158a7c1606d88873c5a1b5764c16cb046b6f2275b2Przemyslaw Szczepaniak 3168a7c1606d88873c5a1b5764c16cb046b6f2275b2Przemyslaw Szczepaniak Object obj = null; 3178a7c1606d88873c5a1b5764c16cb046b6f2275b2Przemyslaw Szczepaniak try { 3188a7c1606d88873c5a1b5764c16cb046b6f2275b2Przemyslaw Szczepaniak 3198a7c1606d88873c5a1b5764c16cb046b6f2275b2Przemyslaw Szczepaniak obj = Providers.startJarVerification(); 3208a7c1606d88873c5a1b5764c16cb046b6f2275b2Przemyslaw Szczepaniak PKCS7 block = new PKCS7(blockBytes); 32129045203f3f694cddea7b3f115ea82f648ba0cbaAlex Klyubin SignerInfo[] verifiedSignerInfos = block.verify(sfBytes); 32229045203f3f694cddea7b3f115ea82f648ba0cbaAlex Klyubin if ((verifiedSignerInfos == null) || (verifiedSignerInfos.length == 0)) { 32329045203f3f694cddea7b3f115ea82f648ba0cbaAlex Klyubin throw new GeneralSecurityException( 32429045203f3f694cddea7b3f115ea82f648ba0cbaAlex Klyubin "Failed to verify signature: no verified SignerInfos"); 3258a7c1606d88873c5a1b5764c16cb046b6f2275b2Przemyslaw Szczepaniak } 32629045203f3f694cddea7b3f115ea82f648ba0cbaAlex Klyubin // Ignore any SignerInfo other than the first one, to be compatible with older Android 32729045203f3f694cddea7b3f115ea82f648ba0cbaAlex Klyubin // platforms which have been doing this for years. See 32829045203f3f694cddea7b3f115ea82f648ba0cbaAlex Klyubin // libcore/luni/src/main/java/org/apache/harmony/security/utils/JarUtils.java 32929045203f3f694cddea7b3f115ea82f648ba0cbaAlex Klyubin // verifySignature method of older platforms. 33029045203f3f694cddea7b3f115ea82f648ba0cbaAlex Klyubin SignerInfo verifiedSignerInfo = verifiedSignerInfos[0]; 33129045203f3f694cddea7b3f115ea82f648ba0cbaAlex Klyubin List<X509Certificate> verifiedSignerCertChain = 33229045203f3f694cddea7b3f115ea82f648ba0cbaAlex Klyubin verifiedSignerInfo.getCertificateChain(block); 33329045203f3f694cddea7b3f115ea82f648ba0cbaAlex Klyubin if (verifiedSignerCertChain == null) { 33429045203f3f694cddea7b3f115ea82f648ba0cbaAlex Klyubin // Should never happen 33529045203f3f694cddea7b3f115ea82f648ba0cbaAlex Klyubin throw new GeneralSecurityException( 33629045203f3f694cddea7b3f115ea82f648ba0cbaAlex Klyubin "Failed to find verified SignerInfo certificate chain"); 33729045203f3f694cddea7b3f115ea82f648ba0cbaAlex Klyubin } else if (verifiedSignerCertChain.isEmpty()) { 33829045203f3f694cddea7b3f115ea82f648ba0cbaAlex Klyubin // Should never happen 33929045203f3f694cddea7b3f115ea82f648ba0cbaAlex Klyubin throw new GeneralSecurityException( 34029045203f3f694cddea7b3f115ea82f648ba0cbaAlex Klyubin "Verified SignerInfo certificate chain is emtpy"); 3418a7c1606d88873c5a1b5764c16cb046b6f2275b2Przemyslaw Szczepaniak } 34229045203f3f694cddea7b3f115ea82f648ba0cbaAlex Klyubin return verifiedSignerCertChain.toArray( 34329045203f3f694cddea7b3f115ea82f648ba0cbaAlex Klyubin new X509Certificate[verifiedSignerCertChain.size()]); 3448a7c1606d88873c5a1b5764c16cb046b6f2275b2Przemyslaw Szczepaniak } catch (IOException e) { 3458a7c1606d88873c5a1b5764c16cb046b6f2275b2Przemyslaw Szczepaniak throw new GeneralSecurityException("IO exception verifying jar cert", e); 3468a7c1606d88873c5a1b5764c16cb046b6f2275b2Przemyslaw Szczepaniak } finally { 3478a7c1606d88873c5a1b5764c16cb046b6f2275b2Przemyslaw Szczepaniak Providers.stopJarVerification(obj); 3488a7c1606d88873c5a1b5764c16cb046b6f2275b2Przemyslaw Szczepaniak } 3498a7c1606d88873c5a1b5764c16cb046b6f2275b2Przemyslaw Szczepaniak } 3508a7c1606d88873c5a1b5764c16cb046b6f2275b2Przemyslaw Szczepaniak 3518a7c1606d88873c5a1b5764c16cb046b6f2275b2Przemyslaw Szczepaniak /** 3528a7c1606d88873c5a1b5764c16cb046b6f2275b2Przemyslaw Szczepaniak * @param certFile 3538a7c1606d88873c5a1b5764c16cb046b6f2275b2Przemyslaw Szczepaniak */ 3548a7c1606d88873c5a1b5764c16cb046b6f2275b2Przemyslaw Szczepaniak private void verifyCertificate(String certFile) { 3558a7c1606d88873c5a1b5764c16cb046b6f2275b2Przemyslaw Szczepaniak // Found Digital Sig, .SF should already have been read 3568a7c1606d88873c5a1b5764c16cb046b6f2275b2Przemyslaw Szczepaniak String signatureFile = certFile.substring(0, certFile.lastIndexOf('.')) + ".SF"; 3578a7c1606d88873c5a1b5764c16cb046b6f2275b2Przemyslaw Szczepaniak byte[] sfBytes = metaEntries.get(signatureFile); 3588a7c1606d88873c5a1b5764c16cb046b6f2275b2Przemyslaw Szczepaniak if (sfBytes == null) { 3598a7c1606d88873c5a1b5764c16cb046b6f2275b2Przemyslaw Szczepaniak return; 3608a7c1606d88873c5a1b5764c16cb046b6f2275b2Przemyslaw Szczepaniak } 3618a7c1606d88873c5a1b5764c16cb046b6f2275b2Przemyslaw Szczepaniak 3628a7c1606d88873c5a1b5764c16cb046b6f2275b2Przemyslaw Szczepaniak byte[] manifestBytes = metaEntries.get(JarFile.MANIFEST_NAME); 3638a7c1606d88873c5a1b5764c16cb046b6f2275b2Przemyslaw Szczepaniak // Manifest entry is required for any verifications. 3648a7c1606d88873c5a1b5764c16cb046b6f2275b2Przemyslaw Szczepaniak if (manifestBytes == null) { 3658a7c1606d88873c5a1b5764c16cb046b6f2275b2Przemyslaw Szczepaniak return; 3668a7c1606d88873c5a1b5764c16cb046b6f2275b2Przemyslaw Szczepaniak } 3678a7c1606d88873c5a1b5764c16cb046b6f2275b2Przemyslaw Szczepaniak 3688a7c1606d88873c5a1b5764c16cb046b6f2275b2Przemyslaw Szczepaniak byte[] sBlockBytes = metaEntries.get(certFile); 3698a7c1606d88873c5a1b5764c16cb046b6f2275b2Przemyslaw Szczepaniak try { 3708a7c1606d88873c5a1b5764c16cb046b6f2275b2Przemyslaw Szczepaniak Certificate[] signerCertChain = verifyBytes(sBlockBytes, sfBytes); 3718a7c1606d88873c5a1b5764c16cb046b6f2275b2Przemyslaw Szczepaniak if (signerCertChain != null) { 3728a7c1606d88873c5a1b5764c16cb046b6f2275b2Przemyslaw Szczepaniak certificates.put(signatureFile, signerCertChain); 3738a7c1606d88873c5a1b5764c16cb046b6f2275b2Przemyslaw Szczepaniak } 3748a7c1606d88873c5a1b5764c16cb046b6f2275b2Przemyslaw Szczepaniak } catch (GeneralSecurityException e) { 3758a7c1606d88873c5a1b5764c16cb046b6f2275b2Przemyslaw Szczepaniak throw failedVerification(jarName, signatureFile, e); 3768a7c1606d88873c5a1b5764c16cb046b6f2275b2Przemyslaw Szczepaniak } 3778a7c1606d88873c5a1b5764c16cb046b6f2275b2Przemyslaw Szczepaniak 3788a7c1606d88873c5a1b5764c16cb046b6f2275b2Przemyslaw Szczepaniak // Verify manifest hash in .sf file 3798a7c1606d88873c5a1b5764c16cb046b6f2275b2Przemyslaw Szczepaniak Attributes attributes = new Attributes(); 3808a7c1606d88873c5a1b5764c16cb046b6f2275b2Przemyslaw Szczepaniak HashMap<String, Attributes> entries = new HashMap<String, Attributes>(); 3818a7c1606d88873c5a1b5764c16cb046b6f2275b2Przemyslaw Szczepaniak try { 3828a7c1606d88873c5a1b5764c16cb046b6f2275b2Przemyslaw Szczepaniak StrictJarManifestReader im = new StrictJarManifestReader(sfBytes, attributes); 3838a7c1606d88873c5a1b5764c16cb046b6f2275b2Przemyslaw Szczepaniak im.readEntries(entries, null); 3848a7c1606d88873c5a1b5764c16cb046b6f2275b2Przemyslaw Szczepaniak } catch (IOException e) { 3858a7c1606d88873c5a1b5764c16cb046b6f2275b2Przemyslaw Szczepaniak return; 3868a7c1606d88873c5a1b5764c16cb046b6f2275b2Przemyslaw Szczepaniak } 3878a7c1606d88873c5a1b5764c16cb046b6f2275b2Przemyslaw Szczepaniak 38867096e08a72beea85979a3aa9fc5b376b2c2b5adDaniel Cashman // If requested, check whether a newer APK Signature Scheme signature was stripped. 3899b59bc459b4cb5415909641bd1e981100bfafb2bAlex Klyubin if (signatureSchemeRollbackProtectionsEnforced) { 3909b59bc459b4cb5415909641bd1e981100bfafb2bAlex Klyubin String apkSignatureSchemeIdList = 39167096e08a72beea85979a3aa9fc5b376b2c2b5adDaniel Cashman attributes.getValue(SF_ATTRIBUTE_ANDROID_APK_SIGNED_NAME); 3929b59bc459b4cb5415909641bd1e981100bfafb2bAlex Klyubin if (apkSignatureSchemeIdList != null) { 3939b59bc459b4cb5415909641bd1e981100bfafb2bAlex Klyubin // This field contains a comma-separated list of APK signature scheme IDs which 3949b59bc459b4cb5415909641bd1e981100bfafb2bAlex Klyubin // were used to sign this APK. If an ID is known to us, it means signatures of that 3959b59bc459b4cb5415909641bd1e981100bfafb2bAlex Klyubin // scheme were stripped from the APK because otherwise we wouldn't have fallen back 3969b59bc459b4cb5415909641bd1e981100bfafb2bAlex Klyubin // to verifying the APK using the JAR signature scheme. 3979b59bc459b4cb5415909641bd1e981100bfafb2bAlex Klyubin boolean v2SignatureGenerated = false; 39867096e08a72beea85979a3aa9fc5b376b2c2b5adDaniel Cashman boolean v3SignatureGenerated = false; 3999b59bc459b4cb5415909641bd1e981100bfafb2bAlex Klyubin StringTokenizer tokenizer = new StringTokenizer(apkSignatureSchemeIdList, ","); 4009b59bc459b4cb5415909641bd1e981100bfafb2bAlex Klyubin while (tokenizer.hasMoreTokens()) { 4019b59bc459b4cb5415909641bd1e981100bfafb2bAlex Klyubin String idText = tokenizer.nextToken().trim(); 4029b59bc459b4cb5415909641bd1e981100bfafb2bAlex Klyubin if (idText.isEmpty()) { 4039b59bc459b4cb5415909641bd1e981100bfafb2bAlex Klyubin continue; 4049b59bc459b4cb5415909641bd1e981100bfafb2bAlex Klyubin } 4059b59bc459b4cb5415909641bd1e981100bfafb2bAlex Klyubin int id; 4069b59bc459b4cb5415909641bd1e981100bfafb2bAlex Klyubin try { 4079b59bc459b4cb5415909641bd1e981100bfafb2bAlex Klyubin id = Integer.parseInt(idText); 4089b59bc459b4cb5415909641bd1e981100bfafb2bAlex Klyubin } catch (Exception ignored) { 4099b59bc459b4cb5415909641bd1e981100bfafb2bAlex Klyubin continue; 4109b59bc459b4cb5415909641bd1e981100bfafb2bAlex Klyubin } 4119b59bc459b4cb5415909641bd1e981100bfafb2bAlex Klyubin if (id == ApkSignatureSchemeV2Verifier.SF_ATTRIBUTE_ANDROID_APK_SIGNED_ID) { 4129b59bc459b4cb5415909641bd1e981100bfafb2bAlex Klyubin // This APK was supposed to be signed with APK Signature Scheme v2 but no 4139b59bc459b4cb5415909641bd1e981100bfafb2bAlex Klyubin // such signature was found. 4149b59bc459b4cb5415909641bd1e981100bfafb2bAlex Klyubin v2SignatureGenerated = true; 4159b59bc459b4cb5415909641bd1e981100bfafb2bAlex Klyubin break; 4169b59bc459b4cb5415909641bd1e981100bfafb2bAlex Klyubin } 41767096e08a72beea85979a3aa9fc5b376b2c2b5adDaniel Cashman if (id == ApkSignatureSchemeV3Verifier.SF_ATTRIBUTE_ANDROID_APK_SIGNED_ID) { 41867096e08a72beea85979a3aa9fc5b376b2c2b5adDaniel Cashman // This APK was supposed to be signed with APK Signature Scheme v3 but no 41967096e08a72beea85979a3aa9fc5b376b2c2b5adDaniel Cashman // such signature was found. 42067096e08a72beea85979a3aa9fc5b376b2c2b5adDaniel Cashman v3SignatureGenerated = true; 42167096e08a72beea85979a3aa9fc5b376b2c2b5adDaniel Cashman break; 42267096e08a72beea85979a3aa9fc5b376b2c2b5adDaniel Cashman } 423e415718502897a4e5385af47d3bbe8c8257c2e5dAlex Klyubin } 424e415718502897a4e5385af47d3bbe8c8257c2e5dAlex Klyubin 4259b59bc459b4cb5415909641bd1e981100bfafb2bAlex Klyubin if (v2SignatureGenerated) { 4269b59bc459b4cb5415909641bd1e981100bfafb2bAlex Klyubin throw new SecurityException(signatureFile + " indicates " + jarName 4279b59bc459b4cb5415909641bd1e981100bfafb2bAlex Klyubin + " is signed using APK Signature Scheme v2, but no such signature was" 4289b59bc459b4cb5415909641bd1e981100bfafb2bAlex Klyubin + " found. Signature stripped?"); 4299b59bc459b4cb5415909641bd1e981100bfafb2bAlex Klyubin } 43067096e08a72beea85979a3aa9fc5b376b2c2b5adDaniel Cashman if (v3SignatureGenerated) { 43167096e08a72beea85979a3aa9fc5b376b2c2b5adDaniel Cashman throw new SecurityException(signatureFile + " indicates " + jarName 43267096e08a72beea85979a3aa9fc5b376b2c2b5adDaniel Cashman + " is signed using APK Signature Scheme v3, but no such signature was" 43367096e08a72beea85979a3aa9fc5b376b2c2b5adDaniel Cashman + " found. Signature stripped?"); 43467096e08a72beea85979a3aa9fc5b376b2c2b5adDaniel Cashman } 435e415718502897a4e5385af47d3bbe8c8257c2e5dAlex Klyubin } 436e415718502897a4e5385af47d3bbe8c8257c2e5dAlex Klyubin } 437e415718502897a4e5385af47d3bbe8c8257c2e5dAlex Klyubin 4388a7c1606d88873c5a1b5764c16cb046b6f2275b2Przemyslaw Szczepaniak // Do we actually have any signatures to look at? 4398a7c1606d88873c5a1b5764c16cb046b6f2275b2Przemyslaw Szczepaniak if (attributes.get(Attributes.Name.SIGNATURE_VERSION) == null) { 4408a7c1606d88873c5a1b5764c16cb046b6f2275b2Przemyslaw Szczepaniak return; 4418a7c1606d88873c5a1b5764c16cb046b6f2275b2Przemyslaw Szczepaniak } 4428a7c1606d88873c5a1b5764c16cb046b6f2275b2Przemyslaw Szczepaniak 4438a7c1606d88873c5a1b5764c16cb046b6f2275b2Przemyslaw Szczepaniak boolean createdBySigntool = false; 4448a7c1606d88873c5a1b5764c16cb046b6f2275b2Przemyslaw Szczepaniak String createdBy = attributes.getValue("Created-By"); 4458a7c1606d88873c5a1b5764c16cb046b6f2275b2Przemyslaw Szczepaniak if (createdBy != null) { 4468a7c1606d88873c5a1b5764c16cb046b6f2275b2Przemyslaw Szczepaniak createdBySigntool = createdBy.indexOf("signtool") != -1; 4478a7c1606d88873c5a1b5764c16cb046b6f2275b2Przemyslaw Szczepaniak } 4488a7c1606d88873c5a1b5764c16cb046b6f2275b2Przemyslaw Szczepaniak 4498a7c1606d88873c5a1b5764c16cb046b6f2275b2Przemyslaw Szczepaniak // Use .SF to verify the mainAttributes of the manifest 4508a7c1606d88873c5a1b5764c16cb046b6f2275b2Przemyslaw Szczepaniak // If there is no -Digest-Manifest-Main-Attributes entry in .SF 4518a7c1606d88873c5a1b5764c16cb046b6f2275b2Przemyslaw Szczepaniak // file, such as those created before java 1.5, then we ignore 4528a7c1606d88873c5a1b5764c16cb046b6f2275b2Przemyslaw Szczepaniak // such verification. 4538a7c1606d88873c5a1b5764c16cb046b6f2275b2Przemyslaw Szczepaniak if (mainAttributesEnd > 0 && !createdBySigntool) { 4548a7c1606d88873c5a1b5764c16cb046b6f2275b2Przemyslaw Szczepaniak String digestAttribute = "-Digest-Manifest-Main-Attributes"; 4558a7c1606d88873c5a1b5764c16cb046b6f2275b2Przemyslaw Szczepaniak if (!verify(attributes, digestAttribute, manifestBytes, 0, mainAttributesEnd, false, true)) { 4568a7c1606d88873c5a1b5764c16cb046b6f2275b2Przemyslaw Szczepaniak throw failedVerification(jarName, signatureFile); 4578a7c1606d88873c5a1b5764c16cb046b6f2275b2Przemyslaw Szczepaniak } 4588a7c1606d88873c5a1b5764c16cb046b6f2275b2Przemyslaw Szczepaniak } 4598a7c1606d88873c5a1b5764c16cb046b6f2275b2Przemyslaw Szczepaniak 4608a7c1606d88873c5a1b5764c16cb046b6f2275b2Przemyslaw Szczepaniak // Use .SF to verify the whole manifest. 4618a7c1606d88873c5a1b5764c16cb046b6f2275b2Przemyslaw Szczepaniak String digestAttribute = createdBySigntool ? "-Digest" : "-Digest-Manifest"; 4628a7c1606d88873c5a1b5764c16cb046b6f2275b2Przemyslaw Szczepaniak if (!verify(attributes, digestAttribute, manifestBytes, 0, manifestBytes.length, false, false)) { 4638a7c1606d88873c5a1b5764c16cb046b6f2275b2Przemyslaw Szczepaniak Iterator<Map.Entry<String, Attributes>> it = entries.entrySet().iterator(); 4648a7c1606d88873c5a1b5764c16cb046b6f2275b2Przemyslaw Szczepaniak while (it.hasNext()) { 4658a7c1606d88873c5a1b5764c16cb046b6f2275b2Przemyslaw Szczepaniak Map.Entry<String, Attributes> entry = it.next(); 4668a7c1606d88873c5a1b5764c16cb046b6f2275b2Przemyslaw Szczepaniak StrictJarManifest.Chunk chunk = manifest.getChunk(entry.getKey()); 4678a7c1606d88873c5a1b5764c16cb046b6f2275b2Przemyslaw Szczepaniak if (chunk == null) { 4688a7c1606d88873c5a1b5764c16cb046b6f2275b2Przemyslaw Szczepaniak return; 4698a7c1606d88873c5a1b5764c16cb046b6f2275b2Przemyslaw Szczepaniak } 4708a7c1606d88873c5a1b5764c16cb046b6f2275b2Przemyslaw Szczepaniak if (!verify(entry.getValue(), "-Digest", manifestBytes, 4718a7c1606d88873c5a1b5764c16cb046b6f2275b2Przemyslaw Szczepaniak chunk.start, chunk.end, createdBySigntool, false)) { 4728a7c1606d88873c5a1b5764c16cb046b6f2275b2Przemyslaw Szczepaniak throw invalidDigest(signatureFile, entry.getKey(), jarName); 4738a7c1606d88873c5a1b5764c16cb046b6f2275b2Przemyslaw Szczepaniak } 4748a7c1606d88873c5a1b5764c16cb046b6f2275b2Przemyslaw Szczepaniak } 4758a7c1606d88873c5a1b5764c16cb046b6f2275b2Przemyslaw Szczepaniak } 4768a7c1606d88873c5a1b5764c16cb046b6f2275b2Przemyslaw Szczepaniak metaEntries.put(signatureFile, null); 4778a7c1606d88873c5a1b5764c16cb046b6f2275b2Przemyslaw Szczepaniak signatures.put(signatureFile, entries); 4788a7c1606d88873c5a1b5764c16cb046b6f2275b2Przemyslaw Szczepaniak } 4798a7c1606d88873c5a1b5764c16cb046b6f2275b2Przemyslaw Szczepaniak 4808a7c1606d88873c5a1b5764c16cb046b6f2275b2Przemyslaw Szczepaniak /** 4818a7c1606d88873c5a1b5764c16cb046b6f2275b2Przemyslaw Szczepaniak * Returns a <code>boolean</code> indication of whether or not the 4828a7c1606d88873c5a1b5764c16cb046b6f2275b2Przemyslaw Szczepaniak * associated jar file is signed. 4838a7c1606d88873c5a1b5764c16cb046b6f2275b2Przemyslaw Szczepaniak * 4848a7c1606d88873c5a1b5764c16cb046b6f2275b2Przemyslaw Szczepaniak * @return {@code true} if the JAR is signed, {@code false} 4858a7c1606d88873c5a1b5764c16cb046b6f2275b2Przemyslaw Szczepaniak * otherwise. 4868a7c1606d88873c5a1b5764c16cb046b6f2275b2Przemyslaw Szczepaniak */ 4878a7c1606d88873c5a1b5764c16cb046b6f2275b2Przemyslaw Szczepaniak boolean isSignedJar() { 4888a7c1606d88873c5a1b5764c16cb046b6f2275b2Przemyslaw Szczepaniak return certificates.size() > 0; 4898a7c1606d88873c5a1b5764c16cb046b6f2275b2Przemyslaw Szczepaniak } 4908a7c1606d88873c5a1b5764c16cb046b6f2275b2Przemyslaw Szczepaniak 4918a7c1606d88873c5a1b5764c16cb046b6f2275b2Przemyslaw Szczepaniak private boolean verify(Attributes attributes, String entry, byte[] data, 4928a7c1606d88873c5a1b5764c16cb046b6f2275b2Przemyslaw Szczepaniak int start, int end, boolean ignoreSecondEndline, boolean ignorable) { 4938a7c1606d88873c5a1b5764c16cb046b6f2275b2Przemyslaw Szczepaniak for (int i = 0; i < DIGEST_ALGORITHMS.length; i++) { 4948a7c1606d88873c5a1b5764c16cb046b6f2275b2Przemyslaw Szczepaniak String algorithm = DIGEST_ALGORITHMS[i]; 4958a7c1606d88873c5a1b5764c16cb046b6f2275b2Przemyslaw Szczepaniak String hash = attributes.getValue(algorithm + entry); 4968a7c1606d88873c5a1b5764c16cb046b6f2275b2Przemyslaw Szczepaniak if (hash == null) { 4978a7c1606d88873c5a1b5764c16cb046b6f2275b2Przemyslaw Szczepaniak continue; 4988a7c1606d88873c5a1b5764c16cb046b6f2275b2Przemyslaw Szczepaniak } 4998a7c1606d88873c5a1b5764c16cb046b6f2275b2Przemyslaw Szczepaniak 5008a7c1606d88873c5a1b5764c16cb046b6f2275b2Przemyslaw Szczepaniak MessageDigest md; 5018a7c1606d88873c5a1b5764c16cb046b6f2275b2Przemyslaw Szczepaniak try { 5028a7c1606d88873c5a1b5764c16cb046b6f2275b2Przemyslaw Szczepaniak md = MessageDigest.getInstance(algorithm); 5038a7c1606d88873c5a1b5764c16cb046b6f2275b2Przemyslaw Szczepaniak } catch (NoSuchAlgorithmException e) { 5048a7c1606d88873c5a1b5764c16cb046b6f2275b2Przemyslaw Szczepaniak continue; 5058a7c1606d88873c5a1b5764c16cb046b6f2275b2Przemyslaw Szczepaniak } 5068a7c1606d88873c5a1b5764c16cb046b6f2275b2Przemyslaw Szczepaniak if (ignoreSecondEndline && data[end - 1] == '\n' && data[end - 2] == '\n') { 5078a7c1606d88873c5a1b5764c16cb046b6f2275b2Przemyslaw Szczepaniak md.update(data, start, end - 1 - start); 5088a7c1606d88873c5a1b5764c16cb046b6f2275b2Przemyslaw Szczepaniak } else { 5098a7c1606d88873c5a1b5764c16cb046b6f2275b2Przemyslaw Szczepaniak md.update(data, start, end - start); 5108a7c1606d88873c5a1b5764c16cb046b6f2275b2Przemyslaw Szczepaniak } 5118a7c1606d88873c5a1b5764c16cb046b6f2275b2Przemyslaw Szczepaniak byte[] b = md.digest(); 5129f00d71787774bf79d4b398c28630f670765b791Tobias Thierer byte[] encodedHashBytes = hash.getBytes(StandardCharsets.ISO_8859_1); 5139f00d71787774bf79d4b398c28630f670765b791Tobias Thierer return verifyMessageDigest(b, encodedHashBytes); 5148a7c1606d88873c5a1b5764c16cb046b6f2275b2Przemyslaw Szczepaniak } 5158a7c1606d88873c5a1b5764c16cb046b6f2275b2Przemyslaw Szczepaniak return ignorable; 5168a7c1606d88873c5a1b5764c16cb046b6f2275b2Przemyslaw Szczepaniak } 5178a7c1606d88873c5a1b5764c16cb046b6f2275b2Przemyslaw Szczepaniak 5189f00d71787774bf79d4b398c28630f670765b791Tobias Thierer private static boolean verifyMessageDigest(byte[] expected, byte[] encodedActual) { 5199f00d71787774bf79d4b398c28630f670765b791Tobias Thierer byte[] actual; 5209f00d71787774bf79d4b398c28630f670765b791Tobias Thierer try { 5219f00d71787774bf79d4b398c28630f670765b791Tobias Thierer actual = java.util.Base64.getDecoder().decode(encodedActual); 5229f00d71787774bf79d4b398c28630f670765b791Tobias Thierer } catch (IllegalArgumentException e) { 5239f00d71787774bf79d4b398c28630f670765b791Tobias Thierer return false; 5249f00d71787774bf79d4b398c28630f670765b791Tobias Thierer } 5259f00d71787774bf79d4b398c28630f670765b791Tobias Thierer return MessageDigest.isEqual(expected, actual); 5269f00d71787774bf79d4b398c28630f670765b791Tobias Thierer } 5279f00d71787774bf79d4b398c28630f670765b791Tobias Thierer 5288a7c1606d88873c5a1b5764c16cb046b6f2275b2Przemyslaw Szczepaniak /** 5298a7c1606d88873c5a1b5764c16cb046b6f2275b2Przemyslaw Szczepaniak * Returns all of the {@link java.security.cert.Certificate} chains that 5308a7c1606d88873c5a1b5764c16cb046b6f2275b2Przemyslaw Szczepaniak * were used to verify the signature on the JAR entry called 5318a7c1606d88873c5a1b5764c16cb046b6f2275b2Przemyslaw Szczepaniak * {@code name}. Callers must not modify the returned arrays. 5328a7c1606d88873c5a1b5764c16cb046b6f2275b2Przemyslaw Szczepaniak * 5338a7c1606d88873c5a1b5764c16cb046b6f2275b2Przemyslaw Szczepaniak * @param name 5348a7c1606d88873c5a1b5764c16cb046b6f2275b2Przemyslaw Szczepaniak * the name of a JAR entry. 5358a7c1606d88873c5a1b5764c16cb046b6f2275b2Przemyslaw Szczepaniak * @return an array of {@link java.security.cert.Certificate} chains. 5368a7c1606d88873c5a1b5764c16cb046b6f2275b2Przemyslaw Szczepaniak */ 5378a7c1606d88873c5a1b5764c16cb046b6f2275b2Przemyslaw Szczepaniak Certificate[][] getCertificateChains(String name) { 5388a7c1606d88873c5a1b5764c16cb046b6f2275b2Przemyslaw Szczepaniak return verifiedEntries.get(name); 5398a7c1606d88873c5a1b5764c16cb046b6f2275b2Przemyslaw Szczepaniak } 5408a7c1606d88873c5a1b5764c16cb046b6f2275b2Przemyslaw Szczepaniak 5418a7c1606d88873c5a1b5764c16cb046b6f2275b2Przemyslaw Szczepaniak /** 5428a7c1606d88873c5a1b5764c16cb046b6f2275b2Przemyslaw Szczepaniak * Remove all entries from the internal collection of data held about each 5438a7c1606d88873c5a1b5764c16cb046b6f2275b2Przemyslaw Szczepaniak * JAR entry in the {@code META-INF} directory. 5448a7c1606d88873c5a1b5764c16cb046b6f2275b2Przemyslaw Szczepaniak */ 5458a7c1606d88873c5a1b5764c16cb046b6f2275b2Przemyslaw Szczepaniak void removeMetaEntries() { 5468a7c1606d88873c5a1b5764c16cb046b6f2275b2Przemyslaw Szczepaniak metaEntries.clear(); 5478a7c1606d88873c5a1b5764c16cb046b6f2275b2Przemyslaw Szczepaniak } 5488a7c1606d88873c5a1b5764c16cb046b6f2275b2Przemyslaw Szczepaniak} 549