StrictJarVerifier.java revision 29045203f3f694cddea7b3f115ea82f648ba0cba
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 208a7c1606d88873c5a1b5764c16cb046b6f2275b2Przemyslaw Szczepaniakimport java.io.ByteArrayInputStream; 218a7c1606d88873c5a1b5764c16cb046b6f2275b2Przemyslaw Szczepaniakimport java.io.IOException; 228a7c1606d88873c5a1b5764c16cb046b6f2275b2Przemyslaw Szczepaniakimport java.io.OutputStream; 238a7c1606d88873c5a1b5764c16cb046b6f2275b2Przemyslaw Szczepaniakimport java.nio.charset.StandardCharsets; 248a7c1606d88873c5a1b5764c16cb046b6f2275b2Przemyslaw Szczepaniakimport java.security.GeneralSecurityException; 258a7c1606d88873c5a1b5764c16cb046b6f2275b2Przemyslaw Szczepaniakimport java.security.MessageDigest; 268a7c1606d88873c5a1b5764c16cb046b6f2275b2Przemyslaw Szczepaniakimport java.security.NoSuchAlgorithmException; 278a7c1606d88873c5a1b5764c16cb046b6f2275b2Przemyslaw Szczepaniakimport java.security.cert.Certificate; 288a7c1606d88873c5a1b5764c16cb046b6f2275b2Przemyslaw Szczepaniakimport java.security.cert.X509Certificate; 298a7c1606d88873c5a1b5764c16cb046b6f2275b2Przemyslaw Szczepaniakimport java.util.ArrayList; 308a7c1606d88873c5a1b5764c16cb046b6f2275b2Przemyslaw Szczepaniakimport java.util.HashMap; 318a7c1606d88873c5a1b5764c16cb046b6f2275b2Przemyslaw Szczepaniakimport java.util.Hashtable; 328a7c1606d88873c5a1b5764c16cb046b6f2275b2Przemyslaw Szczepaniakimport java.util.Iterator; 3329045203f3f694cddea7b3f115ea82f648ba0cbaAlex Klyubinimport java.util.List; 348a7c1606d88873c5a1b5764c16cb046b6f2275b2Przemyslaw Szczepaniakimport java.util.Locale; 358a7c1606d88873c5a1b5764c16cb046b6f2275b2Przemyslaw Szczepaniakimport java.util.Map; 36e415718502897a4e5385af47d3bbe8c8257c2e5dAlex Klyubinimport java.util.Set; 37e415718502897a4e5385af47d3bbe8c8257c2e5dAlex Klyubinimport java.util.StringTokenizer; 388a7c1606d88873c5a1b5764c16cb046b6f2275b2Przemyslaw Szczepaniakimport java.util.jar.Attributes; 398a7c1606d88873c5a1b5764c16cb046b6f2275b2Przemyslaw Szczepaniakimport java.util.jar.JarFile; 40e415718502897a4e5385af47d3bbe8c8257c2e5dAlex Klyubinimport android.util.ArraySet; 41e415718502897a4e5385af47d3bbe8c8257c2e5dAlex Klyubinimport android.util.apk.ApkSignatureSchemeV2Verifier; 428a7c1606d88873c5a1b5764c16cb046b6f2275b2Przemyslaw Szczepaniakimport libcore.io.Base64; 438a7c1606d88873c5a1b5764c16cb046b6f2275b2Przemyslaw Szczepaniakimport sun.security.jca.Providers; 448a7c1606d88873c5a1b5764c16cb046b6f2275b2Przemyslaw Szczepaniakimport sun.security.pkcs.PKCS7; 4529045203f3f694cddea7b3f115ea82f648ba0cbaAlex Klyubinimport sun.security.pkcs.SignerInfo; 468a7c1606d88873c5a1b5764c16cb046b6f2275b2Przemyslaw Szczepaniak 478a7c1606d88873c5a1b5764c16cb046b6f2275b2Przemyslaw Szczepaniak/** 488a7c1606d88873c5a1b5764c16cb046b6f2275b2Przemyslaw Szczepaniak * Non-public class used by {@link JarFile} and {@link JarInputStream} to manage 498a7c1606d88873c5a1b5764c16cb046b6f2275b2Przemyslaw Szczepaniak * the verification of signed JARs. {@code JarFile} and {@code JarInputStream} 508a7c1606d88873c5a1b5764c16cb046b6f2275b2Przemyslaw Szczepaniak * objects are expected to have a {@code JarVerifier} instance member which 518a7c1606d88873c5a1b5764c16cb046b6f2275b2Przemyslaw Szczepaniak * can be used to carry out the tasks associated with verifying a signed JAR. 528a7c1606d88873c5a1b5764c16cb046b6f2275b2Przemyslaw Szczepaniak * These tasks would typically include: 538a7c1606d88873c5a1b5764c16cb046b6f2275b2Przemyslaw Szczepaniak * <ul> 548a7c1606d88873c5a1b5764c16cb046b6f2275b2Przemyslaw Szczepaniak * <li>verification of all signed signature files 558a7c1606d88873c5a1b5764c16cb046b6f2275b2Przemyslaw Szczepaniak * <li>confirmation that all signed data was signed only by the party or parties 568a7c1606d88873c5a1b5764c16cb046b6f2275b2Przemyslaw Szczepaniak * specified in the signature block data 578a7c1606d88873c5a1b5764c16cb046b6f2275b2Przemyslaw Szczepaniak * <li>verification that the contents of all signature files (i.e. {@code .SF} 588a7c1606d88873c5a1b5764c16cb046b6f2275b2Przemyslaw Szczepaniak * files) agree with the JAR entries information found in the JAR manifest. 598a7c1606d88873c5a1b5764c16cb046b6f2275b2Przemyslaw Szczepaniak * </ul> 608a7c1606d88873c5a1b5764c16cb046b6f2275b2Przemyslaw Szczepaniak */ 618a7c1606d88873c5a1b5764c16cb046b6f2275b2Przemyslaw Szczepaniakclass StrictJarVerifier { 628a7c1606d88873c5a1b5764c16cb046b6f2275b2Przemyslaw Szczepaniak /** 638a7c1606d88873c5a1b5764c16cb046b6f2275b2Przemyslaw Szczepaniak * List of accepted digest algorithms. This list is in order from most 648a7c1606d88873c5a1b5764c16cb046b6f2275b2Przemyslaw Szczepaniak * preferred to least preferred. 658a7c1606d88873c5a1b5764c16cb046b6f2275b2Przemyslaw Szczepaniak */ 668a7c1606d88873c5a1b5764c16cb046b6f2275b2Przemyslaw Szczepaniak private static final String[] DIGEST_ALGORITHMS = new String[] { 678a7c1606d88873c5a1b5764c16cb046b6f2275b2Przemyslaw Szczepaniak "SHA-512", 688a7c1606d88873c5a1b5764c16cb046b6f2275b2Przemyslaw Szczepaniak "SHA-384", 698a7c1606d88873c5a1b5764c16cb046b6f2275b2Przemyslaw Szczepaniak "SHA-256", 708a7c1606d88873c5a1b5764c16cb046b6f2275b2Przemyslaw Szczepaniak "SHA1", 718a7c1606d88873c5a1b5764c16cb046b6f2275b2Przemyslaw Szczepaniak }; 728a7c1606d88873c5a1b5764c16cb046b6f2275b2Przemyslaw Szczepaniak 738a7c1606d88873c5a1b5764c16cb046b6f2275b2Przemyslaw Szczepaniak private final String jarName; 748a7c1606d88873c5a1b5764c16cb046b6f2275b2Przemyslaw Szczepaniak private final StrictJarManifest manifest; 758a7c1606d88873c5a1b5764c16cb046b6f2275b2Przemyslaw Szczepaniak private final HashMap<String, byte[]> metaEntries; 768a7c1606d88873c5a1b5764c16cb046b6f2275b2Przemyslaw Szczepaniak private final int mainAttributesEnd; 779b59bc459b4cb5415909641bd1e981100bfafb2bAlex Klyubin private final boolean signatureSchemeRollbackProtectionsEnforced; 788a7c1606d88873c5a1b5764c16cb046b6f2275b2Przemyslaw Szczepaniak 798a7c1606d88873c5a1b5764c16cb046b6f2275b2Przemyslaw Szczepaniak private final Hashtable<String, HashMap<String, Attributes>> signatures = 808a7c1606d88873c5a1b5764c16cb046b6f2275b2Przemyslaw Szczepaniak new Hashtable<String, HashMap<String, Attributes>>(5); 818a7c1606d88873c5a1b5764c16cb046b6f2275b2Przemyslaw Szczepaniak 828a7c1606d88873c5a1b5764c16cb046b6f2275b2Przemyslaw Szczepaniak private final Hashtable<String, Certificate[]> certificates = 838a7c1606d88873c5a1b5764c16cb046b6f2275b2Przemyslaw Szczepaniak new Hashtable<String, Certificate[]>(5); 848a7c1606d88873c5a1b5764c16cb046b6f2275b2Przemyslaw Szczepaniak 858a7c1606d88873c5a1b5764c16cb046b6f2275b2Przemyslaw Szczepaniak private final Hashtable<String, Certificate[][]> verifiedEntries = 868a7c1606d88873c5a1b5764c16cb046b6f2275b2Przemyslaw Szczepaniak new Hashtable<String, Certificate[][]>(); 878a7c1606d88873c5a1b5764c16cb046b6f2275b2Przemyslaw Szczepaniak 888a7c1606d88873c5a1b5764c16cb046b6f2275b2Przemyslaw Szczepaniak /** 898a7c1606d88873c5a1b5764c16cb046b6f2275b2Przemyslaw Szczepaniak * Stores and a hash and a message digest and verifies that massage digest 908a7c1606d88873c5a1b5764c16cb046b6f2275b2Przemyslaw Szczepaniak * matches the hash. 918a7c1606d88873c5a1b5764c16cb046b6f2275b2Przemyslaw Szczepaniak */ 928a7c1606d88873c5a1b5764c16cb046b6f2275b2Przemyslaw Szczepaniak static class VerifierEntry extends OutputStream { 938a7c1606d88873c5a1b5764c16cb046b6f2275b2Przemyslaw Szczepaniak 948a7c1606d88873c5a1b5764c16cb046b6f2275b2Przemyslaw Szczepaniak private final String name; 958a7c1606d88873c5a1b5764c16cb046b6f2275b2Przemyslaw Szczepaniak 968a7c1606d88873c5a1b5764c16cb046b6f2275b2Przemyslaw Szczepaniak private final MessageDigest digest; 978a7c1606d88873c5a1b5764c16cb046b6f2275b2Przemyslaw Szczepaniak 988a7c1606d88873c5a1b5764c16cb046b6f2275b2Przemyslaw Szczepaniak private final byte[] hash; 998a7c1606d88873c5a1b5764c16cb046b6f2275b2Przemyslaw Szczepaniak 1008a7c1606d88873c5a1b5764c16cb046b6f2275b2Przemyslaw Szczepaniak private final Certificate[][] certChains; 1018a7c1606d88873c5a1b5764c16cb046b6f2275b2Przemyslaw Szczepaniak 1028a7c1606d88873c5a1b5764c16cb046b6f2275b2Przemyslaw Szczepaniak private final Hashtable<String, Certificate[][]> verifiedEntries; 1038a7c1606d88873c5a1b5764c16cb046b6f2275b2Przemyslaw Szczepaniak 1048a7c1606d88873c5a1b5764c16cb046b6f2275b2Przemyslaw Szczepaniak VerifierEntry(String name, MessageDigest digest, byte[] hash, 1058a7c1606d88873c5a1b5764c16cb046b6f2275b2Przemyslaw Szczepaniak Certificate[][] certChains, Hashtable<String, Certificate[][]> verifedEntries) { 1068a7c1606d88873c5a1b5764c16cb046b6f2275b2Przemyslaw Szczepaniak this.name = name; 1078a7c1606d88873c5a1b5764c16cb046b6f2275b2Przemyslaw Szczepaniak this.digest = digest; 1088a7c1606d88873c5a1b5764c16cb046b6f2275b2Przemyslaw Szczepaniak this.hash = hash; 1098a7c1606d88873c5a1b5764c16cb046b6f2275b2Przemyslaw Szczepaniak this.certChains = certChains; 1108a7c1606d88873c5a1b5764c16cb046b6f2275b2Przemyslaw Szczepaniak this.verifiedEntries = verifedEntries; 1118a7c1606d88873c5a1b5764c16cb046b6f2275b2Przemyslaw Szczepaniak } 1128a7c1606d88873c5a1b5764c16cb046b6f2275b2Przemyslaw Szczepaniak 1138a7c1606d88873c5a1b5764c16cb046b6f2275b2Przemyslaw Szczepaniak /** 1148a7c1606d88873c5a1b5764c16cb046b6f2275b2Przemyslaw Szczepaniak * Updates a digest with one byte. 1158a7c1606d88873c5a1b5764c16cb046b6f2275b2Przemyslaw Szczepaniak */ 1168a7c1606d88873c5a1b5764c16cb046b6f2275b2Przemyslaw Szczepaniak @Override 1178a7c1606d88873c5a1b5764c16cb046b6f2275b2Przemyslaw Szczepaniak public void write(int value) { 1188a7c1606d88873c5a1b5764c16cb046b6f2275b2Przemyslaw Szczepaniak digest.update((byte) value); 1198a7c1606d88873c5a1b5764c16cb046b6f2275b2Przemyslaw Szczepaniak } 1208a7c1606d88873c5a1b5764c16cb046b6f2275b2Przemyslaw Szczepaniak 1218a7c1606d88873c5a1b5764c16cb046b6f2275b2Przemyslaw Szczepaniak /** 1228a7c1606d88873c5a1b5764c16cb046b6f2275b2Przemyslaw Szczepaniak * Updates a digest with byte array. 1238a7c1606d88873c5a1b5764c16cb046b6f2275b2Przemyslaw Szczepaniak */ 1248a7c1606d88873c5a1b5764c16cb046b6f2275b2Przemyslaw Szczepaniak @Override 1258a7c1606d88873c5a1b5764c16cb046b6f2275b2Przemyslaw Szczepaniak public void write(byte[] buf, int off, int nbytes) { 1268a7c1606d88873c5a1b5764c16cb046b6f2275b2Przemyslaw Szczepaniak digest.update(buf, off, nbytes); 1278a7c1606d88873c5a1b5764c16cb046b6f2275b2Przemyslaw Szczepaniak } 1288a7c1606d88873c5a1b5764c16cb046b6f2275b2Przemyslaw Szczepaniak 1298a7c1606d88873c5a1b5764c16cb046b6f2275b2Przemyslaw Szczepaniak /** 1308a7c1606d88873c5a1b5764c16cb046b6f2275b2Przemyslaw Szczepaniak * Verifies that the digests stored in the manifest match the decrypted 1318a7c1606d88873c5a1b5764c16cb046b6f2275b2Przemyslaw Szczepaniak * digests from the .SF file. This indicates the validity of the 1328a7c1606d88873c5a1b5764c16cb046b6f2275b2Przemyslaw Szczepaniak * signing, not the integrity of the file, as its digest must be 1338a7c1606d88873c5a1b5764c16cb046b6f2275b2Przemyslaw Szczepaniak * calculated and verified when its contents are read. 1348a7c1606d88873c5a1b5764c16cb046b6f2275b2Przemyslaw Szczepaniak * 1358a7c1606d88873c5a1b5764c16cb046b6f2275b2Przemyslaw Szczepaniak * @throws SecurityException 1368a7c1606d88873c5a1b5764c16cb046b6f2275b2Przemyslaw Szczepaniak * if the digest value stored in the manifest does <i>not</i> 1378a7c1606d88873c5a1b5764c16cb046b6f2275b2Przemyslaw Szczepaniak * agree with the decrypted digest as recovered from the 1388a7c1606d88873c5a1b5764c16cb046b6f2275b2Przemyslaw Szczepaniak * <code>.SF</code> file. 1398a7c1606d88873c5a1b5764c16cb046b6f2275b2Przemyslaw Szczepaniak */ 1408a7c1606d88873c5a1b5764c16cb046b6f2275b2Przemyslaw Szczepaniak void verify() { 1418a7c1606d88873c5a1b5764c16cb046b6f2275b2Przemyslaw Szczepaniak byte[] d = digest.digest(); 1428a7c1606d88873c5a1b5764c16cb046b6f2275b2Przemyslaw Szczepaniak if (!MessageDigest.isEqual(d, Base64.decode(hash))) { 1438a7c1606d88873c5a1b5764c16cb046b6f2275b2Przemyslaw Szczepaniak throw invalidDigest(JarFile.MANIFEST_NAME, name, name); 1448a7c1606d88873c5a1b5764c16cb046b6f2275b2Przemyslaw Szczepaniak } 1458a7c1606d88873c5a1b5764c16cb046b6f2275b2Przemyslaw Szczepaniak verifiedEntries.put(name, certChains); 1468a7c1606d88873c5a1b5764c16cb046b6f2275b2Przemyslaw Szczepaniak } 1478a7c1606d88873c5a1b5764c16cb046b6f2275b2Przemyslaw Szczepaniak } 1488a7c1606d88873c5a1b5764c16cb046b6f2275b2Przemyslaw Szczepaniak 1498a7c1606d88873c5a1b5764c16cb046b6f2275b2Przemyslaw Szczepaniak private static SecurityException invalidDigest(String signatureFile, String name, 1508a7c1606d88873c5a1b5764c16cb046b6f2275b2Przemyslaw Szczepaniak String jarName) { 1518a7c1606d88873c5a1b5764c16cb046b6f2275b2Przemyslaw Szczepaniak throw new SecurityException(signatureFile + " has invalid digest for " + name + 1528a7c1606d88873c5a1b5764c16cb046b6f2275b2Przemyslaw Szczepaniak " in " + jarName); 1538a7c1606d88873c5a1b5764c16cb046b6f2275b2Przemyslaw Szczepaniak } 1548a7c1606d88873c5a1b5764c16cb046b6f2275b2Przemyslaw Szczepaniak 1558a7c1606d88873c5a1b5764c16cb046b6f2275b2Przemyslaw Szczepaniak private static SecurityException failedVerification(String jarName, String signatureFile) { 1568a7c1606d88873c5a1b5764c16cb046b6f2275b2Przemyslaw Szczepaniak throw new SecurityException(jarName + " failed verification of " + signatureFile); 1578a7c1606d88873c5a1b5764c16cb046b6f2275b2Przemyslaw Szczepaniak } 1588a7c1606d88873c5a1b5764c16cb046b6f2275b2Przemyslaw Szczepaniak 1598a7c1606d88873c5a1b5764c16cb046b6f2275b2Przemyslaw Szczepaniak private static SecurityException failedVerification(String jarName, String signatureFile, 1608a7c1606d88873c5a1b5764c16cb046b6f2275b2Przemyslaw Szczepaniak Throwable e) { 1618a7c1606d88873c5a1b5764c16cb046b6f2275b2Przemyslaw Szczepaniak throw new SecurityException(jarName + " failed verification of " + signatureFile, e); 1628a7c1606d88873c5a1b5764c16cb046b6f2275b2Przemyslaw Szczepaniak } 1638a7c1606d88873c5a1b5764c16cb046b6f2275b2Przemyslaw Szczepaniak 1648a7c1606d88873c5a1b5764c16cb046b6f2275b2Przemyslaw Szczepaniak 1658a7c1606d88873c5a1b5764c16cb046b6f2275b2Przemyslaw Szczepaniak /** 1668a7c1606d88873c5a1b5764c16cb046b6f2275b2Przemyslaw Szczepaniak * Constructs and returns a new instance of {@code JarVerifier}. 1678a7c1606d88873c5a1b5764c16cb046b6f2275b2Przemyslaw Szczepaniak * 1688a7c1606d88873c5a1b5764c16cb046b6f2275b2Przemyslaw Szczepaniak * @param name 1698a7c1606d88873c5a1b5764c16cb046b6f2275b2Przemyslaw Szczepaniak * the name of the JAR file being verified. 1709b59bc459b4cb5415909641bd1e981100bfafb2bAlex Klyubin * 1719b59bc459b4cb5415909641bd1e981100bfafb2bAlex Klyubin * @param signatureSchemeRollbackProtectionsEnforced {@code true} to enforce protections against 1729b59bc459b4cb5415909641bd1e981100bfafb2bAlex Klyubin * stripping newer signature schemes (e.g., APK Signature Scheme v2) from the file, or 1739b59bc459b4cb5415909641bd1e981100bfafb2bAlex Klyubin * {@code false} to ignore any such protections. 1748a7c1606d88873c5a1b5764c16cb046b6f2275b2Przemyslaw Szczepaniak */ 1758a7c1606d88873c5a1b5764c16cb046b6f2275b2Przemyslaw Szczepaniak StrictJarVerifier(String name, StrictJarManifest manifest, 1769b59bc459b4cb5415909641bd1e981100bfafb2bAlex Klyubin HashMap<String, byte[]> metaEntries, boolean signatureSchemeRollbackProtectionsEnforced) { 1778a7c1606d88873c5a1b5764c16cb046b6f2275b2Przemyslaw Szczepaniak jarName = name; 1788a7c1606d88873c5a1b5764c16cb046b6f2275b2Przemyslaw Szczepaniak this.manifest = manifest; 1798a7c1606d88873c5a1b5764c16cb046b6f2275b2Przemyslaw Szczepaniak this.metaEntries = metaEntries; 1808a7c1606d88873c5a1b5764c16cb046b6f2275b2Przemyslaw Szczepaniak this.mainAttributesEnd = manifest.getMainAttributesEnd(); 1819b59bc459b4cb5415909641bd1e981100bfafb2bAlex Klyubin this.signatureSchemeRollbackProtectionsEnforced = 1829b59bc459b4cb5415909641bd1e981100bfafb2bAlex Klyubin signatureSchemeRollbackProtectionsEnforced; 1838a7c1606d88873c5a1b5764c16cb046b6f2275b2Przemyslaw Szczepaniak } 1848a7c1606d88873c5a1b5764c16cb046b6f2275b2Przemyslaw Szczepaniak 1858a7c1606d88873c5a1b5764c16cb046b6f2275b2Przemyslaw Szczepaniak /** 1868a7c1606d88873c5a1b5764c16cb046b6f2275b2Przemyslaw Szczepaniak * Invoked for each new JAR entry read operation from the input 1878a7c1606d88873c5a1b5764c16cb046b6f2275b2Przemyslaw Szczepaniak * stream. This method constructs and returns a new {@link VerifierEntry} 1888a7c1606d88873c5a1b5764c16cb046b6f2275b2Przemyslaw Szczepaniak * which contains the certificates used to sign the entry and its hash value 1898a7c1606d88873c5a1b5764c16cb046b6f2275b2Przemyslaw Szczepaniak * as specified in the JAR MANIFEST format. 1908a7c1606d88873c5a1b5764c16cb046b6f2275b2Przemyslaw Szczepaniak * 1918a7c1606d88873c5a1b5764c16cb046b6f2275b2Przemyslaw Szczepaniak * @param name 1928a7c1606d88873c5a1b5764c16cb046b6f2275b2Przemyslaw Szczepaniak * the name of an entry in a JAR file which is <b>not</b> in the 1938a7c1606d88873c5a1b5764c16cb046b6f2275b2Przemyslaw Szczepaniak * {@code META-INF} directory. 1948a7c1606d88873c5a1b5764c16cb046b6f2275b2Przemyslaw Szczepaniak * @return a new instance of {@link VerifierEntry} which can be used by 1958a7c1606d88873c5a1b5764c16cb046b6f2275b2Przemyslaw Szczepaniak * callers as an {@link OutputStream}. 1968a7c1606d88873c5a1b5764c16cb046b6f2275b2Przemyslaw Szczepaniak */ 1978a7c1606d88873c5a1b5764c16cb046b6f2275b2Przemyslaw Szczepaniak VerifierEntry initEntry(String name) { 1988a7c1606d88873c5a1b5764c16cb046b6f2275b2Przemyslaw Szczepaniak // If no manifest is present by the time an entry is found, 1998a7c1606d88873c5a1b5764c16cb046b6f2275b2Przemyslaw Szczepaniak // verification cannot occur. If no signature files have 2008a7c1606d88873c5a1b5764c16cb046b6f2275b2Przemyslaw Szczepaniak // been found, do not verify. 2018a7c1606d88873c5a1b5764c16cb046b6f2275b2Przemyslaw Szczepaniak if (manifest == null || signatures.isEmpty()) { 2028a7c1606d88873c5a1b5764c16cb046b6f2275b2Przemyslaw Szczepaniak return null; 2038a7c1606d88873c5a1b5764c16cb046b6f2275b2Przemyslaw Szczepaniak } 2048a7c1606d88873c5a1b5764c16cb046b6f2275b2Przemyslaw Szczepaniak 2058a7c1606d88873c5a1b5764c16cb046b6f2275b2Przemyslaw Szczepaniak Attributes attributes = manifest.getAttributes(name); 2068a7c1606d88873c5a1b5764c16cb046b6f2275b2Przemyslaw Szczepaniak // entry has no digest 2078a7c1606d88873c5a1b5764c16cb046b6f2275b2Przemyslaw Szczepaniak if (attributes == null) { 2088a7c1606d88873c5a1b5764c16cb046b6f2275b2Przemyslaw Szczepaniak return null; 2098a7c1606d88873c5a1b5764c16cb046b6f2275b2Przemyslaw Szczepaniak } 2108a7c1606d88873c5a1b5764c16cb046b6f2275b2Przemyslaw Szczepaniak 2118a7c1606d88873c5a1b5764c16cb046b6f2275b2Przemyslaw Szczepaniak ArrayList<Certificate[]> certChains = new ArrayList<Certificate[]>(); 2128a7c1606d88873c5a1b5764c16cb046b6f2275b2Przemyslaw Szczepaniak Iterator<Map.Entry<String, HashMap<String, Attributes>>> it = signatures.entrySet().iterator(); 2138a7c1606d88873c5a1b5764c16cb046b6f2275b2Przemyslaw Szczepaniak while (it.hasNext()) { 2148a7c1606d88873c5a1b5764c16cb046b6f2275b2Przemyslaw Szczepaniak Map.Entry<String, HashMap<String, Attributes>> entry = it.next(); 2158a7c1606d88873c5a1b5764c16cb046b6f2275b2Przemyslaw Szczepaniak HashMap<String, Attributes> hm = entry.getValue(); 2168a7c1606d88873c5a1b5764c16cb046b6f2275b2Przemyslaw Szczepaniak if (hm.get(name) != null) { 2178a7c1606d88873c5a1b5764c16cb046b6f2275b2Przemyslaw Szczepaniak // Found an entry for entry name in .SF file 2188a7c1606d88873c5a1b5764c16cb046b6f2275b2Przemyslaw Szczepaniak String signatureFile = entry.getKey(); 2198a7c1606d88873c5a1b5764c16cb046b6f2275b2Przemyslaw Szczepaniak Certificate[] certChain = certificates.get(signatureFile); 2208a7c1606d88873c5a1b5764c16cb046b6f2275b2Przemyslaw Szczepaniak if (certChain != null) { 2218a7c1606d88873c5a1b5764c16cb046b6f2275b2Przemyslaw Szczepaniak certChains.add(certChain); 2228a7c1606d88873c5a1b5764c16cb046b6f2275b2Przemyslaw Szczepaniak } 2238a7c1606d88873c5a1b5764c16cb046b6f2275b2Przemyslaw Szczepaniak } 2248a7c1606d88873c5a1b5764c16cb046b6f2275b2Przemyslaw Szczepaniak } 2258a7c1606d88873c5a1b5764c16cb046b6f2275b2Przemyslaw Szczepaniak 2268a7c1606d88873c5a1b5764c16cb046b6f2275b2Przemyslaw Szczepaniak // entry is not signed 2278a7c1606d88873c5a1b5764c16cb046b6f2275b2Przemyslaw Szczepaniak if (certChains.isEmpty()) { 2288a7c1606d88873c5a1b5764c16cb046b6f2275b2Przemyslaw Szczepaniak return null; 2298a7c1606d88873c5a1b5764c16cb046b6f2275b2Przemyslaw Szczepaniak } 2308a7c1606d88873c5a1b5764c16cb046b6f2275b2Przemyslaw Szczepaniak Certificate[][] certChainsArray = certChains.toArray(new Certificate[certChains.size()][]); 2318a7c1606d88873c5a1b5764c16cb046b6f2275b2Przemyslaw Szczepaniak 2328a7c1606d88873c5a1b5764c16cb046b6f2275b2Przemyslaw Szczepaniak for (int i = 0; i < DIGEST_ALGORITHMS.length; i++) { 2338a7c1606d88873c5a1b5764c16cb046b6f2275b2Przemyslaw Szczepaniak final String algorithm = DIGEST_ALGORITHMS[i]; 2348a7c1606d88873c5a1b5764c16cb046b6f2275b2Przemyslaw Szczepaniak final String hash = attributes.getValue(algorithm + "-Digest"); 2358a7c1606d88873c5a1b5764c16cb046b6f2275b2Przemyslaw Szczepaniak if (hash == null) { 2368a7c1606d88873c5a1b5764c16cb046b6f2275b2Przemyslaw Szczepaniak continue; 2378a7c1606d88873c5a1b5764c16cb046b6f2275b2Przemyslaw Szczepaniak } 2388a7c1606d88873c5a1b5764c16cb046b6f2275b2Przemyslaw Szczepaniak byte[] hashBytes = hash.getBytes(StandardCharsets.ISO_8859_1); 2398a7c1606d88873c5a1b5764c16cb046b6f2275b2Przemyslaw Szczepaniak 2408a7c1606d88873c5a1b5764c16cb046b6f2275b2Przemyslaw Szczepaniak try { 2418a7c1606d88873c5a1b5764c16cb046b6f2275b2Przemyslaw Szczepaniak return new VerifierEntry(name, MessageDigest.getInstance(algorithm), hashBytes, 2428a7c1606d88873c5a1b5764c16cb046b6f2275b2Przemyslaw Szczepaniak certChainsArray, verifiedEntries); 2438a7c1606d88873c5a1b5764c16cb046b6f2275b2Przemyslaw Szczepaniak } catch (NoSuchAlgorithmException ignored) { 2448a7c1606d88873c5a1b5764c16cb046b6f2275b2Przemyslaw Szczepaniak } 2458a7c1606d88873c5a1b5764c16cb046b6f2275b2Przemyslaw Szczepaniak } 2468a7c1606d88873c5a1b5764c16cb046b6f2275b2Przemyslaw Szczepaniak return null; 2478a7c1606d88873c5a1b5764c16cb046b6f2275b2Przemyslaw Szczepaniak } 2488a7c1606d88873c5a1b5764c16cb046b6f2275b2Przemyslaw Szczepaniak 2498a7c1606d88873c5a1b5764c16cb046b6f2275b2Przemyslaw Szczepaniak /** 2508a7c1606d88873c5a1b5764c16cb046b6f2275b2Przemyslaw Szczepaniak * Add a new meta entry to the internal collection of data held on each JAR 2518a7c1606d88873c5a1b5764c16cb046b6f2275b2Przemyslaw Szczepaniak * entry in the {@code META-INF} directory including the manifest 2528a7c1606d88873c5a1b5764c16cb046b6f2275b2Przemyslaw Szczepaniak * file itself. Files associated with the signing of a JAR would also be 2538a7c1606d88873c5a1b5764c16cb046b6f2275b2Przemyslaw Szczepaniak * added to this collection. 2548a7c1606d88873c5a1b5764c16cb046b6f2275b2Przemyslaw Szczepaniak * 2558a7c1606d88873c5a1b5764c16cb046b6f2275b2Przemyslaw Szczepaniak * @param name 2568a7c1606d88873c5a1b5764c16cb046b6f2275b2Przemyslaw Szczepaniak * the name of the file located in the {@code META-INF} 2578a7c1606d88873c5a1b5764c16cb046b6f2275b2Przemyslaw Szczepaniak * directory. 2588a7c1606d88873c5a1b5764c16cb046b6f2275b2Przemyslaw Szczepaniak * @param buf 2598a7c1606d88873c5a1b5764c16cb046b6f2275b2Przemyslaw Szczepaniak * the file bytes for the file called {@code name}. 2608a7c1606d88873c5a1b5764c16cb046b6f2275b2Przemyslaw Szczepaniak * @see #removeMetaEntries() 2618a7c1606d88873c5a1b5764c16cb046b6f2275b2Przemyslaw Szczepaniak */ 2628a7c1606d88873c5a1b5764c16cb046b6f2275b2Przemyslaw Szczepaniak void addMetaEntry(String name, byte[] buf) { 2638a7c1606d88873c5a1b5764c16cb046b6f2275b2Przemyslaw Szczepaniak metaEntries.put(name.toUpperCase(Locale.US), buf); 2648a7c1606d88873c5a1b5764c16cb046b6f2275b2Przemyslaw Szczepaniak } 2658a7c1606d88873c5a1b5764c16cb046b6f2275b2Przemyslaw Szczepaniak 2668a7c1606d88873c5a1b5764c16cb046b6f2275b2Przemyslaw Szczepaniak /** 2678a7c1606d88873c5a1b5764c16cb046b6f2275b2Przemyslaw Szczepaniak * If the associated JAR file is signed, check on the validity of all of the 2688a7c1606d88873c5a1b5764c16cb046b6f2275b2Przemyslaw Szczepaniak * known signatures. 2698a7c1606d88873c5a1b5764c16cb046b6f2275b2Przemyslaw Szczepaniak * 2708a7c1606d88873c5a1b5764c16cb046b6f2275b2Przemyslaw Szczepaniak * @return {@code true} if the associated JAR is signed and an internal 2718a7c1606d88873c5a1b5764c16cb046b6f2275b2Przemyslaw Szczepaniak * check verifies the validity of the signature(s). {@code false} if 2728a7c1606d88873c5a1b5764c16cb046b6f2275b2Przemyslaw Szczepaniak * the associated JAR file has no entries at all in its {@code 2738a7c1606d88873c5a1b5764c16cb046b6f2275b2Przemyslaw Szczepaniak * META-INF} directory. This situation is indicative of an invalid 2748a7c1606d88873c5a1b5764c16cb046b6f2275b2Przemyslaw Szczepaniak * JAR file. 2758a7c1606d88873c5a1b5764c16cb046b6f2275b2Przemyslaw Szczepaniak * <p> 2768a7c1606d88873c5a1b5764c16cb046b6f2275b2Przemyslaw Szczepaniak * Will also return {@code true} if the JAR file is <i>not</i> 2778a7c1606d88873c5a1b5764c16cb046b6f2275b2Przemyslaw Szczepaniak * signed. 2788a7c1606d88873c5a1b5764c16cb046b6f2275b2Przemyslaw Szczepaniak * @throws SecurityException 2798a7c1606d88873c5a1b5764c16cb046b6f2275b2Przemyslaw Szczepaniak * if the JAR file is signed and it is determined that a 2808a7c1606d88873c5a1b5764c16cb046b6f2275b2Przemyslaw Szczepaniak * signature block file contains an invalid signature for the 2818a7c1606d88873c5a1b5764c16cb046b6f2275b2Przemyslaw Szczepaniak * corresponding signature file. 2828a7c1606d88873c5a1b5764c16cb046b6f2275b2Przemyslaw Szczepaniak */ 2838a7c1606d88873c5a1b5764c16cb046b6f2275b2Przemyslaw Szczepaniak synchronized boolean readCertificates() { 2848a7c1606d88873c5a1b5764c16cb046b6f2275b2Przemyslaw Szczepaniak if (metaEntries.isEmpty()) { 2858a7c1606d88873c5a1b5764c16cb046b6f2275b2Przemyslaw Szczepaniak return false; 2868a7c1606d88873c5a1b5764c16cb046b6f2275b2Przemyslaw Szczepaniak } 2878a7c1606d88873c5a1b5764c16cb046b6f2275b2Przemyslaw Szczepaniak 2888a7c1606d88873c5a1b5764c16cb046b6f2275b2Przemyslaw Szczepaniak Iterator<String> it = metaEntries.keySet().iterator(); 2898a7c1606d88873c5a1b5764c16cb046b6f2275b2Przemyslaw Szczepaniak while (it.hasNext()) { 2908a7c1606d88873c5a1b5764c16cb046b6f2275b2Przemyslaw Szczepaniak String key = it.next(); 2918a7c1606d88873c5a1b5764c16cb046b6f2275b2Przemyslaw Szczepaniak if (key.endsWith(".DSA") || key.endsWith(".RSA") || key.endsWith(".EC")) { 2928a7c1606d88873c5a1b5764c16cb046b6f2275b2Przemyslaw Szczepaniak verifyCertificate(key); 2938a7c1606d88873c5a1b5764c16cb046b6f2275b2Przemyslaw Szczepaniak it.remove(); 2948a7c1606d88873c5a1b5764c16cb046b6f2275b2Przemyslaw Szczepaniak } 2958a7c1606d88873c5a1b5764c16cb046b6f2275b2Przemyslaw Szczepaniak } 2968a7c1606d88873c5a1b5764c16cb046b6f2275b2Przemyslaw Szczepaniak return true; 2978a7c1606d88873c5a1b5764c16cb046b6f2275b2Przemyslaw Szczepaniak } 2988a7c1606d88873c5a1b5764c16cb046b6f2275b2Przemyslaw Szczepaniak 2998a7c1606d88873c5a1b5764c16cb046b6f2275b2Przemyslaw Szczepaniak /** 3008a7c1606d88873c5a1b5764c16cb046b6f2275b2Przemyslaw Szczepaniak * Verifies that the signature computed from {@code sfBytes} matches 3018a7c1606d88873c5a1b5764c16cb046b6f2275b2Przemyslaw Szczepaniak * that specified in {@code blockBytes} (which is a PKCS7 block). Returns 3028a7c1606d88873c5a1b5764c16cb046b6f2275b2Przemyslaw Szczepaniak * certificates listed in the PKCS7 block. Throws a {@code GeneralSecurityException} 3038a7c1606d88873c5a1b5764c16cb046b6f2275b2Przemyslaw Szczepaniak * if something goes wrong during verification. 3048a7c1606d88873c5a1b5764c16cb046b6f2275b2Przemyslaw Szczepaniak */ 3058a7c1606d88873c5a1b5764c16cb046b6f2275b2Przemyslaw Szczepaniak static Certificate[] verifyBytes(byte[] blockBytes, byte[] sfBytes) 3068a7c1606d88873c5a1b5764c16cb046b6f2275b2Przemyslaw Szczepaniak throws GeneralSecurityException { 3078a7c1606d88873c5a1b5764c16cb046b6f2275b2Przemyslaw Szczepaniak 3088a7c1606d88873c5a1b5764c16cb046b6f2275b2Przemyslaw Szczepaniak Object obj = null; 3098a7c1606d88873c5a1b5764c16cb046b6f2275b2Przemyslaw Szczepaniak try { 3108a7c1606d88873c5a1b5764c16cb046b6f2275b2Przemyslaw Szczepaniak 3118a7c1606d88873c5a1b5764c16cb046b6f2275b2Przemyslaw Szczepaniak obj = Providers.startJarVerification(); 3128a7c1606d88873c5a1b5764c16cb046b6f2275b2Przemyslaw Szczepaniak PKCS7 block = new PKCS7(blockBytes); 31329045203f3f694cddea7b3f115ea82f648ba0cbaAlex Klyubin SignerInfo[] verifiedSignerInfos = block.verify(sfBytes); 31429045203f3f694cddea7b3f115ea82f648ba0cbaAlex Klyubin if ((verifiedSignerInfos == null) || (verifiedSignerInfos.length == 0)) { 31529045203f3f694cddea7b3f115ea82f648ba0cbaAlex Klyubin throw new GeneralSecurityException( 31629045203f3f694cddea7b3f115ea82f648ba0cbaAlex Klyubin "Failed to verify signature: no verified SignerInfos"); 3178a7c1606d88873c5a1b5764c16cb046b6f2275b2Przemyslaw Szczepaniak } 31829045203f3f694cddea7b3f115ea82f648ba0cbaAlex Klyubin // Ignore any SignerInfo other than the first one, to be compatible with older Android 31929045203f3f694cddea7b3f115ea82f648ba0cbaAlex Klyubin // platforms which have been doing this for years. See 32029045203f3f694cddea7b3f115ea82f648ba0cbaAlex Klyubin // libcore/luni/src/main/java/org/apache/harmony/security/utils/JarUtils.java 32129045203f3f694cddea7b3f115ea82f648ba0cbaAlex Klyubin // verifySignature method of older platforms. 32229045203f3f694cddea7b3f115ea82f648ba0cbaAlex Klyubin SignerInfo verifiedSignerInfo = verifiedSignerInfos[0]; 32329045203f3f694cddea7b3f115ea82f648ba0cbaAlex Klyubin List<X509Certificate> verifiedSignerCertChain = 32429045203f3f694cddea7b3f115ea82f648ba0cbaAlex Klyubin verifiedSignerInfo.getCertificateChain(block); 32529045203f3f694cddea7b3f115ea82f648ba0cbaAlex Klyubin if (verifiedSignerCertChain == null) { 32629045203f3f694cddea7b3f115ea82f648ba0cbaAlex Klyubin // Should never happen 32729045203f3f694cddea7b3f115ea82f648ba0cbaAlex Klyubin throw new GeneralSecurityException( 32829045203f3f694cddea7b3f115ea82f648ba0cbaAlex Klyubin "Failed to find verified SignerInfo certificate chain"); 32929045203f3f694cddea7b3f115ea82f648ba0cbaAlex Klyubin } else if (verifiedSignerCertChain.isEmpty()) { 33029045203f3f694cddea7b3f115ea82f648ba0cbaAlex Klyubin // Should never happen 33129045203f3f694cddea7b3f115ea82f648ba0cbaAlex Klyubin throw new GeneralSecurityException( 33229045203f3f694cddea7b3f115ea82f648ba0cbaAlex Klyubin "Verified SignerInfo certificate chain is emtpy"); 3338a7c1606d88873c5a1b5764c16cb046b6f2275b2Przemyslaw Szczepaniak } 33429045203f3f694cddea7b3f115ea82f648ba0cbaAlex Klyubin return verifiedSignerCertChain.toArray( 33529045203f3f694cddea7b3f115ea82f648ba0cbaAlex Klyubin new X509Certificate[verifiedSignerCertChain.size()]); 3368a7c1606d88873c5a1b5764c16cb046b6f2275b2Przemyslaw Szczepaniak } catch (IOException e) { 3378a7c1606d88873c5a1b5764c16cb046b6f2275b2Przemyslaw Szczepaniak throw new GeneralSecurityException("IO exception verifying jar cert", e); 3388a7c1606d88873c5a1b5764c16cb046b6f2275b2Przemyslaw Szczepaniak } finally { 3398a7c1606d88873c5a1b5764c16cb046b6f2275b2Przemyslaw Szczepaniak Providers.stopJarVerification(obj); 3408a7c1606d88873c5a1b5764c16cb046b6f2275b2Przemyslaw Szczepaniak } 3418a7c1606d88873c5a1b5764c16cb046b6f2275b2Przemyslaw Szczepaniak } 3428a7c1606d88873c5a1b5764c16cb046b6f2275b2Przemyslaw Szczepaniak 3438a7c1606d88873c5a1b5764c16cb046b6f2275b2Przemyslaw Szczepaniak /** 3448a7c1606d88873c5a1b5764c16cb046b6f2275b2Przemyslaw Szczepaniak * @param certFile 3458a7c1606d88873c5a1b5764c16cb046b6f2275b2Przemyslaw Szczepaniak */ 3468a7c1606d88873c5a1b5764c16cb046b6f2275b2Przemyslaw Szczepaniak private void verifyCertificate(String certFile) { 3478a7c1606d88873c5a1b5764c16cb046b6f2275b2Przemyslaw Szczepaniak // Found Digital Sig, .SF should already have been read 3488a7c1606d88873c5a1b5764c16cb046b6f2275b2Przemyslaw Szczepaniak String signatureFile = certFile.substring(0, certFile.lastIndexOf('.')) + ".SF"; 3498a7c1606d88873c5a1b5764c16cb046b6f2275b2Przemyslaw Szczepaniak byte[] sfBytes = metaEntries.get(signatureFile); 3508a7c1606d88873c5a1b5764c16cb046b6f2275b2Przemyslaw Szczepaniak if (sfBytes == null) { 3518a7c1606d88873c5a1b5764c16cb046b6f2275b2Przemyslaw Szczepaniak return; 3528a7c1606d88873c5a1b5764c16cb046b6f2275b2Przemyslaw Szczepaniak } 3538a7c1606d88873c5a1b5764c16cb046b6f2275b2Przemyslaw Szczepaniak 3548a7c1606d88873c5a1b5764c16cb046b6f2275b2Przemyslaw Szczepaniak byte[] manifestBytes = metaEntries.get(JarFile.MANIFEST_NAME); 3558a7c1606d88873c5a1b5764c16cb046b6f2275b2Przemyslaw Szczepaniak // Manifest entry is required for any verifications. 3568a7c1606d88873c5a1b5764c16cb046b6f2275b2Przemyslaw Szczepaniak if (manifestBytes == null) { 3578a7c1606d88873c5a1b5764c16cb046b6f2275b2Przemyslaw Szczepaniak return; 3588a7c1606d88873c5a1b5764c16cb046b6f2275b2Przemyslaw Szczepaniak } 3598a7c1606d88873c5a1b5764c16cb046b6f2275b2Przemyslaw Szczepaniak 3608a7c1606d88873c5a1b5764c16cb046b6f2275b2Przemyslaw Szczepaniak byte[] sBlockBytes = metaEntries.get(certFile); 3618a7c1606d88873c5a1b5764c16cb046b6f2275b2Przemyslaw Szczepaniak try { 3628a7c1606d88873c5a1b5764c16cb046b6f2275b2Przemyslaw Szczepaniak Certificate[] signerCertChain = verifyBytes(sBlockBytes, sfBytes); 3638a7c1606d88873c5a1b5764c16cb046b6f2275b2Przemyslaw Szczepaniak if (signerCertChain != null) { 3648a7c1606d88873c5a1b5764c16cb046b6f2275b2Przemyslaw Szczepaniak certificates.put(signatureFile, signerCertChain); 3658a7c1606d88873c5a1b5764c16cb046b6f2275b2Przemyslaw Szczepaniak } 3668a7c1606d88873c5a1b5764c16cb046b6f2275b2Przemyslaw Szczepaniak } catch (GeneralSecurityException e) { 3678a7c1606d88873c5a1b5764c16cb046b6f2275b2Przemyslaw Szczepaniak throw failedVerification(jarName, signatureFile, e); 3688a7c1606d88873c5a1b5764c16cb046b6f2275b2Przemyslaw Szczepaniak } 3698a7c1606d88873c5a1b5764c16cb046b6f2275b2Przemyslaw Szczepaniak 3708a7c1606d88873c5a1b5764c16cb046b6f2275b2Przemyslaw Szczepaniak // Verify manifest hash in .sf file 3718a7c1606d88873c5a1b5764c16cb046b6f2275b2Przemyslaw Szczepaniak Attributes attributes = new Attributes(); 3728a7c1606d88873c5a1b5764c16cb046b6f2275b2Przemyslaw Szczepaniak HashMap<String, Attributes> entries = new HashMap<String, Attributes>(); 3738a7c1606d88873c5a1b5764c16cb046b6f2275b2Przemyslaw Szczepaniak try { 3748a7c1606d88873c5a1b5764c16cb046b6f2275b2Przemyslaw Szczepaniak StrictJarManifestReader im = new StrictJarManifestReader(sfBytes, attributes); 3758a7c1606d88873c5a1b5764c16cb046b6f2275b2Przemyslaw Szczepaniak im.readEntries(entries, null); 3768a7c1606d88873c5a1b5764c16cb046b6f2275b2Przemyslaw Szczepaniak } catch (IOException e) { 3778a7c1606d88873c5a1b5764c16cb046b6f2275b2Przemyslaw Szczepaniak return; 3788a7c1606d88873c5a1b5764c16cb046b6f2275b2Przemyslaw Szczepaniak } 3798a7c1606d88873c5a1b5764c16cb046b6f2275b2Przemyslaw Szczepaniak 3809b59bc459b4cb5415909641bd1e981100bfafb2bAlex Klyubin // If requested, check whether APK Signature Scheme v2 signature was stripped. 3819b59bc459b4cb5415909641bd1e981100bfafb2bAlex Klyubin if (signatureSchemeRollbackProtectionsEnforced) { 3829b59bc459b4cb5415909641bd1e981100bfafb2bAlex Klyubin String apkSignatureSchemeIdList = 3839b59bc459b4cb5415909641bd1e981100bfafb2bAlex Klyubin attributes.getValue( 3849b59bc459b4cb5415909641bd1e981100bfafb2bAlex Klyubin ApkSignatureSchemeV2Verifier.SF_ATTRIBUTE_ANDROID_APK_SIGNED_NAME); 3859b59bc459b4cb5415909641bd1e981100bfafb2bAlex Klyubin if (apkSignatureSchemeIdList != null) { 3869b59bc459b4cb5415909641bd1e981100bfafb2bAlex Klyubin // This field contains a comma-separated list of APK signature scheme IDs which 3879b59bc459b4cb5415909641bd1e981100bfafb2bAlex Klyubin // were used to sign this APK. If an ID is known to us, it means signatures of that 3889b59bc459b4cb5415909641bd1e981100bfafb2bAlex Klyubin // scheme were stripped from the APK because otherwise we wouldn't have fallen back 3899b59bc459b4cb5415909641bd1e981100bfafb2bAlex Klyubin // to verifying the APK using the JAR signature scheme. 3909b59bc459b4cb5415909641bd1e981100bfafb2bAlex Klyubin boolean v2SignatureGenerated = false; 3919b59bc459b4cb5415909641bd1e981100bfafb2bAlex Klyubin StringTokenizer tokenizer = new StringTokenizer(apkSignatureSchemeIdList, ","); 3929b59bc459b4cb5415909641bd1e981100bfafb2bAlex Klyubin while (tokenizer.hasMoreTokens()) { 3939b59bc459b4cb5415909641bd1e981100bfafb2bAlex Klyubin String idText = tokenizer.nextToken().trim(); 3949b59bc459b4cb5415909641bd1e981100bfafb2bAlex Klyubin if (idText.isEmpty()) { 3959b59bc459b4cb5415909641bd1e981100bfafb2bAlex Klyubin continue; 3969b59bc459b4cb5415909641bd1e981100bfafb2bAlex Klyubin } 3979b59bc459b4cb5415909641bd1e981100bfafb2bAlex Klyubin int id; 3989b59bc459b4cb5415909641bd1e981100bfafb2bAlex Klyubin try { 3999b59bc459b4cb5415909641bd1e981100bfafb2bAlex Klyubin id = Integer.parseInt(idText); 4009b59bc459b4cb5415909641bd1e981100bfafb2bAlex Klyubin } catch (Exception ignored) { 4019b59bc459b4cb5415909641bd1e981100bfafb2bAlex Klyubin continue; 4029b59bc459b4cb5415909641bd1e981100bfafb2bAlex Klyubin } 4039b59bc459b4cb5415909641bd1e981100bfafb2bAlex Klyubin if (id == ApkSignatureSchemeV2Verifier.SF_ATTRIBUTE_ANDROID_APK_SIGNED_ID) { 4049b59bc459b4cb5415909641bd1e981100bfafb2bAlex Klyubin // This APK was supposed to be signed with APK Signature Scheme v2 but no 4059b59bc459b4cb5415909641bd1e981100bfafb2bAlex Klyubin // such signature was found. 4069b59bc459b4cb5415909641bd1e981100bfafb2bAlex Klyubin v2SignatureGenerated = true; 4079b59bc459b4cb5415909641bd1e981100bfafb2bAlex Klyubin break; 4089b59bc459b4cb5415909641bd1e981100bfafb2bAlex Klyubin } 409e415718502897a4e5385af47d3bbe8c8257c2e5dAlex Klyubin } 410e415718502897a4e5385af47d3bbe8c8257c2e5dAlex Klyubin 4119b59bc459b4cb5415909641bd1e981100bfafb2bAlex Klyubin if (v2SignatureGenerated) { 4129b59bc459b4cb5415909641bd1e981100bfafb2bAlex Klyubin throw new SecurityException(signatureFile + " indicates " + jarName 4139b59bc459b4cb5415909641bd1e981100bfafb2bAlex Klyubin + " is signed using APK Signature Scheme v2, but no such signature was" 4149b59bc459b4cb5415909641bd1e981100bfafb2bAlex Klyubin + " found. Signature stripped?"); 4159b59bc459b4cb5415909641bd1e981100bfafb2bAlex Klyubin } 416e415718502897a4e5385af47d3bbe8c8257c2e5dAlex Klyubin } 417e415718502897a4e5385af47d3bbe8c8257c2e5dAlex Klyubin } 418e415718502897a4e5385af47d3bbe8c8257c2e5dAlex Klyubin 4198a7c1606d88873c5a1b5764c16cb046b6f2275b2Przemyslaw Szczepaniak // Do we actually have any signatures to look at? 4208a7c1606d88873c5a1b5764c16cb046b6f2275b2Przemyslaw Szczepaniak if (attributes.get(Attributes.Name.SIGNATURE_VERSION) == null) { 4218a7c1606d88873c5a1b5764c16cb046b6f2275b2Przemyslaw Szczepaniak return; 4228a7c1606d88873c5a1b5764c16cb046b6f2275b2Przemyslaw Szczepaniak } 4238a7c1606d88873c5a1b5764c16cb046b6f2275b2Przemyslaw Szczepaniak 4248a7c1606d88873c5a1b5764c16cb046b6f2275b2Przemyslaw Szczepaniak boolean createdBySigntool = false; 4258a7c1606d88873c5a1b5764c16cb046b6f2275b2Przemyslaw Szczepaniak String createdBy = attributes.getValue("Created-By"); 4268a7c1606d88873c5a1b5764c16cb046b6f2275b2Przemyslaw Szczepaniak if (createdBy != null) { 4278a7c1606d88873c5a1b5764c16cb046b6f2275b2Przemyslaw Szczepaniak createdBySigntool = createdBy.indexOf("signtool") != -1; 4288a7c1606d88873c5a1b5764c16cb046b6f2275b2Przemyslaw Szczepaniak } 4298a7c1606d88873c5a1b5764c16cb046b6f2275b2Przemyslaw Szczepaniak 4308a7c1606d88873c5a1b5764c16cb046b6f2275b2Przemyslaw Szczepaniak // Use .SF to verify the mainAttributes of the manifest 4318a7c1606d88873c5a1b5764c16cb046b6f2275b2Przemyslaw Szczepaniak // If there is no -Digest-Manifest-Main-Attributes entry in .SF 4328a7c1606d88873c5a1b5764c16cb046b6f2275b2Przemyslaw Szczepaniak // file, such as those created before java 1.5, then we ignore 4338a7c1606d88873c5a1b5764c16cb046b6f2275b2Przemyslaw Szczepaniak // such verification. 4348a7c1606d88873c5a1b5764c16cb046b6f2275b2Przemyslaw Szczepaniak if (mainAttributesEnd > 0 && !createdBySigntool) { 4358a7c1606d88873c5a1b5764c16cb046b6f2275b2Przemyslaw Szczepaniak String digestAttribute = "-Digest-Manifest-Main-Attributes"; 4368a7c1606d88873c5a1b5764c16cb046b6f2275b2Przemyslaw Szczepaniak if (!verify(attributes, digestAttribute, manifestBytes, 0, mainAttributesEnd, false, true)) { 4378a7c1606d88873c5a1b5764c16cb046b6f2275b2Przemyslaw Szczepaniak throw failedVerification(jarName, signatureFile); 4388a7c1606d88873c5a1b5764c16cb046b6f2275b2Przemyslaw Szczepaniak } 4398a7c1606d88873c5a1b5764c16cb046b6f2275b2Przemyslaw Szczepaniak } 4408a7c1606d88873c5a1b5764c16cb046b6f2275b2Przemyslaw Szczepaniak 4418a7c1606d88873c5a1b5764c16cb046b6f2275b2Przemyslaw Szczepaniak // Use .SF to verify the whole manifest. 4428a7c1606d88873c5a1b5764c16cb046b6f2275b2Przemyslaw Szczepaniak String digestAttribute = createdBySigntool ? "-Digest" : "-Digest-Manifest"; 4438a7c1606d88873c5a1b5764c16cb046b6f2275b2Przemyslaw Szczepaniak if (!verify(attributes, digestAttribute, manifestBytes, 0, manifestBytes.length, false, false)) { 4448a7c1606d88873c5a1b5764c16cb046b6f2275b2Przemyslaw Szczepaniak Iterator<Map.Entry<String, Attributes>> it = entries.entrySet().iterator(); 4458a7c1606d88873c5a1b5764c16cb046b6f2275b2Przemyslaw Szczepaniak while (it.hasNext()) { 4468a7c1606d88873c5a1b5764c16cb046b6f2275b2Przemyslaw Szczepaniak Map.Entry<String, Attributes> entry = it.next(); 4478a7c1606d88873c5a1b5764c16cb046b6f2275b2Przemyslaw Szczepaniak StrictJarManifest.Chunk chunk = manifest.getChunk(entry.getKey()); 4488a7c1606d88873c5a1b5764c16cb046b6f2275b2Przemyslaw Szczepaniak if (chunk == null) { 4498a7c1606d88873c5a1b5764c16cb046b6f2275b2Przemyslaw Szczepaniak return; 4508a7c1606d88873c5a1b5764c16cb046b6f2275b2Przemyslaw Szczepaniak } 4518a7c1606d88873c5a1b5764c16cb046b6f2275b2Przemyslaw Szczepaniak if (!verify(entry.getValue(), "-Digest", manifestBytes, 4528a7c1606d88873c5a1b5764c16cb046b6f2275b2Przemyslaw Szczepaniak chunk.start, chunk.end, createdBySigntool, false)) { 4538a7c1606d88873c5a1b5764c16cb046b6f2275b2Przemyslaw Szczepaniak throw invalidDigest(signatureFile, entry.getKey(), jarName); 4548a7c1606d88873c5a1b5764c16cb046b6f2275b2Przemyslaw Szczepaniak } 4558a7c1606d88873c5a1b5764c16cb046b6f2275b2Przemyslaw Szczepaniak } 4568a7c1606d88873c5a1b5764c16cb046b6f2275b2Przemyslaw Szczepaniak } 4578a7c1606d88873c5a1b5764c16cb046b6f2275b2Przemyslaw Szczepaniak metaEntries.put(signatureFile, null); 4588a7c1606d88873c5a1b5764c16cb046b6f2275b2Przemyslaw Szczepaniak signatures.put(signatureFile, entries); 4598a7c1606d88873c5a1b5764c16cb046b6f2275b2Przemyslaw Szczepaniak } 4608a7c1606d88873c5a1b5764c16cb046b6f2275b2Przemyslaw Szczepaniak 4618a7c1606d88873c5a1b5764c16cb046b6f2275b2Przemyslaw Szczepaniak /** 4628a7c1606d88873c5a1b5764c16cb046b6f2275b2Przemyslaw Szczepaniak * Returns a <code>boolean</code> indication of whether or not the 4638a7c1606d88873c5a1b5764c16cb046b6f2275b2Przemyslaw Szczepaniak * associated jar file is signed. 4648a7c1606d88873c5a1b5764c16cb046b6f2275b2Przemyslaw Szczepaniak * 4658a7c1606d88873c5a1b5764c16cb046b6f2275b2Przemyslaw Szczepaniak * @return {@code true} if the JAR is signed, {@code false} 4668a7c1606d88873c5a1b5764c16cb046b6f2275b2Przemyslaw Szczepaniak * otherwise. 4678a7c1606d88873c5a1b5764c16cb046b6f2275b2Przemyslaw Szczepaniak */ 4688a7c1606d88873c5a1b5764c16cb046b6f2275b2Przemyslaw Szczepaniak boolean isSignedJar() { 4698a7c1606d88873c5a1b5764c16cb046b6f2275b2Przemyslaw Szczepaniak return certificates.size() > 0; 4708a7c1606d88873c5a1b5764c16cb046b6f2275b2Przemyslaw Szczepaniak } 4718a7c1606d88873c5a1b5764c16cb046b6f2275b2Przemyslaw Szczepaniak 4728a7c1606d88873c5a1b5764c16cb046b6f2275b2Przemyslaw Szczepaniak private boolean verify(Attributes attributes, String entry, byte[] data, 4738a7c1606d88873c5a1b5764c16cb046b6f2275b2Przemyslaw Szczepaniak int start, int end, boolean ignoreSecondEndline, boolean ignorable) { 4748a7c1606d88873c5a1b5764c16cb046b6f2275b2Przemyslaw Szczepaniak for (int i = 0; i < DIGEST_ALGORITHMS.length; i++) { 4758a7c1606d88873c5a1b5764c16cb046b6f2275b2Przemyslaw Szczepaniak String algorithm = DIGEST_ALGORITHMS[i]; 4768a7c1606d88873c5a1b5764c16cb046b6f2275b2Przemyslaw Szczepaniak String hash = attributes.getValue(algorithm + entry); 4778a7c1606d88873c5a1b5764c16cb046b6f2275b2Przemyslaw Szczepaniak if (hash == null) { 4788a7c1606d88873c5a1b5764c16cb046b6f2275b2Przemyslaw Szczepaniak continue; 4798a7c1606d88873c5a1b5764c16cb046b6f2275b2Przemyslaw Szczepaniak } 4808a7c1606d88873c5a1b5764c16cb046b6f2275b2Przemyslaw Szczepaniak 4818a7c1606d88873c5a1b5764c16cb046b6f2275b2Przemyslaw Szczepaniak MessageDigest md; 4828a7c1606d88873c5a1b5764c16cb046b6f2275b2Przemyslaw Szczepaniak try { 4838a7c1606d88873c5a1b5764c16cb046b6f2275b2Przemyslaw Szczepaniak md = MessageDigest.getInstance(algorithm); 4848a7c1606d88873c5a1b5764c16cb046b6f2275b2Przemyslaw Szczepaniak } catch (NoSuchAlgorithmException e) { 4858a7c1606d88873c5a1b5764c16cb046b6f2275b2Przemyslaw Szczepaniak continue; 4868a7c1606d88873c5a1b5764c16cb046b6f2275b2Przemyslaw Szczepaniak } 4878a7c1606d88873c5a1b5764c16cb046b6f2275b2Przemyslaw Szczepaniak if (ignoreSecondEndline && data[end - 1] == '\n' && data[end - 2] == '\n') { 4888a7c1606d88873c5a1b5764c16cb046b6f2275b2Przemyslaw Szczepaniak md.update(data, start, end - 1 - start); 4898a7c1606d88873c5a1b5764c16cb046b6f2275b2Przemyslaw Szczepaniak } else { 4908a7c1606d88873c5a1b5764c16cb046b6f2275b2Przemyslaw Szczepaniak md.update(data, start, end - start); 4918a7c1606d88873c5a1b5764c16cb046b6f2275b2Przemyslaw Szczepaniak } 4928a7c1606d88873c5a1b5764c16cb046b6f2275b2Przemyslaw Szczepaniak byte[] b = md.digest(); 4938a7c1606d88873c5a1b5764c16cb046b6f2275b2Przemyslaw Szczepaniak byte[] hashBytes = hash.getBytes(StandardCharsets.ISO_8859_1); 4948a7c1606d88873c5a1b5764c16cb046b6f2275b2Przemyslaw Szczepaniak return MessageDigest.isEqual(b, Base64.decode(hashBytes)); 4958a7c1606d88873c5a1b5764c16cb046b6f2275b2Przemyslaw Szczepaniak } 4968a7c1606d88873c5a1b5764c16cb046b6f2275b2Przemyslaw Szczepaniak return ignorable; 4978a7c1606d88873c5a1b5764c16cb046b6f2275b2Przemyslaw Szczepaniak } 4988a7c1606d88873c5a1b5764c16cb046b6f2275b2Przemyslaw Szczepaniak 4998a7c1606d88873c5a1b5764c16cb046b6f2275b2Przemyslaw Szczepaniak /** 5008a7c1606d88873c5a1b5764c16cb046b6f2275b2Przemyslaw Szczepaniak * Returns all of the {@link java.security.cert.Certificate} chains that 5018a7c1606d88873c5a1b5764c16cb046b6f2275b2Przemyslaw Szczepaniak * were used to verify the signature on the JAR entry called 5028a7c1606d88873c5a1b5764c16cb046b6f2275b2Przemyslaw Szczepaniak * {@code name}. Callers must not modify the returned arrays. 5038a7c1606d88873c5a1b5764c16cb046b6f2275b2Przemyslaw Szczepaniak * 5048a7c1606d88873c5a1b5764c16cb046b6f2275b2Przemyslaw Szczepaniak * @param name 5058a7c1606d88873c5a1b5764c16cb046b6f2275b2Przemyslaw Szczepaniak * the name of a JAR entry. 5068a7c1606d88873c5a1b5764c16cb046b6f2275b2Przemyslaw Szczepaniak * @return an array of {@link java.security.cert.Certificate} chains. 5078a7c1606d88873c5a1b5764c16cb046b6f2275b2Przemyslaw Szczepaniak */ 5088a7c1606d88873c5a1b5764c16cb046b6f2275b2Przemyslaw Szczepaniak Certificate[][] getCertificateChains(String name) { 5098a7c1606d88873c5a1b5764c16cb046b6f2275b2Przemyslaw Szczepaniak return verifiedEntries.get(name); 5108a7c1606d88873c5a1b5764c16cb046b6f2275b2Przemyslaw Szczepaniak } 5118a7c1606d88873c5a1b5764c16cb046b6f2275b2Przemyslaw Szczepaniak 5128a7c1606d88873c5a1b5764c16cb046b6f2275b2Przemyslaw Szczepaniak /** 5138a7c1606d88873c5a1b5764c16cb046b6f2275b2Przemyslaw Szczepaniak * Remove all entries from the internal collection of data held about each 5148a7c1606d88873c5a1b5764c16cb046b6f2275b2Przemyslaw Szczepaniak * JAR entry in the {@code META-INF} directory. 5158a7c1606d88873c5a1b5764c16cb046b6f2275b2Przemyslaw Szczepaniak */ 5168a7c1606d88873c5a1b5764c16cb046b6f2275b2Przemyslaw Szczepaniak void removeMetaEntries() { 5178a7c1606d88873c5a1b5764c16cb046b6f2275b2Przemyslaw Szczepaniak metaEntries.clear(); 5188a7c1606d88873c5a1b5764c16cb046b6f2275b2Przemyslaw Szczepaniak } 5198a7c1606d88873c5a1b5764c16cb046b6f2275b2Przemyslaw Szczepaniak} 520