1 /* 2 * Copyright (C) 2018 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package com.android.internal.security; 18 19 import android.annotation.NonNull; 20 import android.annotation.Nullable; 21 import android.os.Build; 22 import android.os.SystemProperties; 23 import android.os.incremental.V4Signature; 24 import android.system.Os; 25 import android.system.OsConstants; 26 import android.util.Slog; 27 28 import com.android.internal.annotations.VisibleForTesting; 29 import com.android.internal.org.bouncycastle.asn1.nist.NISTObjectIdentifiers; 30 import com.android.internal.org.bouncycastle.asn1.pkcs.PKCSObjectIdentifiers; 31 import com.android.internal.org.bouncycastle.cms.CMSException; 32 import com.android.internal.org.bouncycastle.cms.CMSProcessableByteArray; 33 import com.android.internal.org.bouncycastle.cms.CMSSignedData; 34 import com.android.internal.org.bouncycastle.cms.SignerInformation; 35 import com.android.internal.org.bouncycastle.cms.SignerInformationVerifier; 36 import com.android.internal.org.bouncycastle.cms.jcajce.JcaSimpleSignerInfoVerifierBuilder; 37 import com.android.internal.org.bouncycastle.operator.OperatorCreationException; 38 39 import java.io.File; 40 import java.io.IOException; 41 import java.io.InputStream; 42 import java.nio.ByteBuffer; 43 import java.nio.ByteOrder; 44 import java.nio.charset.StandardCharsets; 45 import java.security.DigestException; 46 import java.security.MessageDigest; 47 import java.security.NoSuchAlgorithmException; 48 import java.security.cert.CertificateException; 49 import java.security.cert.CertificateFactory; 50 import java.security.cert.X509Certificate; 51 52 /** Provides fsverity related operations. */ 53 public abstract class VerityUtils { 54 private static final String TAG = "VerityUtils"; 55 56 /** 57 * File extension of the signature file. For example, foo.apk.fsv_sig is the signature file of 58 * foo.apk. 59 */ 60 public static final String FSVERITY_SIGNATURE_FILE_EXTENSION = ".fsv_sig"; 61 62 /** SHA256 hash size. */ 63 private static final int HASH_SIZE_BYTES = 32; 64 isFsVeritySupported()65 public static boolean isFsVeritySupported() { 66 return Build.VERSION.DEVICE_INITIAL_SDK_INT >= Build.VERSION_CODES.R 67 || SystemProperties.getInt("ro.apk_verity.mode", 0) == 2; 68 } 69 70 /** Returns true if the given file looks like containing an fs-verity signature. */ isFsveritySignatureFile(File file)71 public static boolean isFsveritySignatureFile(File file) { 72 return file.getName().endsWith(FSVERITY_SIGNATURE_FILE_EXTENSION); 73 } 74 75 /** Returns the fs-verity signature file path of the given file. */ getFsveritySignatureFilePath(String filePath)76 public static String getFsveritySignatureFilePath(String filePath) { 77 return filePath + FSVERITY_SIGNATURE_FILE_EXTENSION; 78 } 79 80 /** Enables fs-verity for the file without signature. */ setUpFsverity(@onNull String filePath)81 public static void setUpFsverity(@NonNull String filePath) throws IOException { 82 int errno = enableFsverityNative(filePath); 83 if (errno != 0) { 84 throw new IOException("Failed to enable fs-verity on " + filePath + ": " 85 + Os.strerror(errno)); 86 } 87 } 88 89 /** Enables fs-verity for an open file without signature. */ setUpFsverity(int fd)90 public static void setUpFsverity(int fd) throws IOException { 91 int errno = enableFsverityForFdNative(fd); 92 if (errno != 0) { 93 throw new IOException("Failed to enable fs-verity on FD(" + fd + "): " 94 + Os.strerror(errno)); 95 } 96 } 97 98 /** Returns whether the file has fs-verity enabled. */ hasFsverity(@onNull String filePath)99 public static boolean hasFsverity(@NonNull String filePath) { 100 int retval = statxForFsverityNative(filePath); 101 if (retval < 0) { 102 Slog.e(TAG, "Failed to check whether fs-verity is enabled, errno " + -retval + ": " 103 + filePath); 104 return false; 105 } 106 return (retval == 1); 107 } 108 109 /** 110 * Verifies the signature over the fs-verity digest using the provided certificate. 111 * 112 * This method should only be used by any existing fs-verity use cases that require 113 * PKCS#7 signature verification, if backward compatibility is necessary. 114 * 115 * Since PKCS#7 is too flexible, for the current specific need, only specific configuration 116 * will be accepted: 117 * <ul> 118 * <li>Must use SHA256 as the digest algorithm 119 * <li>Must use rsaEncryption as signature algorithm 120 * <li>Must be detached / without content 121 * <li>Must not include any signed or unsigned attributes 122 * </ul> 123 * 124 * It is up to the caller to provide an appropriate/trusted certificate. 125 * 126 * @param signatureBlock byte array of a PKCS#7 detached signature 127 * @param digest fs-verity digest with the common configuration using sha256 128 * @param derCertInputStream an input stream of a X.509 certificate in DER 129 * @return whether the verification succeeds 130 */ verifyPkcs7DetachedSignature(@onNull byte[] signatureBlock, @NonNull byte[] digest, @NonNull InputStream derCertInputStream)131 public static boolean verifyPkcs7DetachedSignature(@NonNull byte[] signatureBlock, 132 @NonNull byte[] digest, @NonNull InputStream derCertInputStream) { 133 if (digest.length != 32) { 134 Slog.w(TAG, "Only sha256 is currently supported"); 135 return false; 136 } 137 138 try { 139 CMSSignedData signedData = new CMSSignedData( 140 new CMSProcessableByteArray(toFormattedDigest(digest)), 141 signatureBlock); 142 143 if (!signedData.isDetachedSignature()) { 144 Slog.w(TAG, "Expect only detached siganture"); 145 return false; 146 } 147 if (!signedData.getCertificates().getMatches(null).isEmpty()) { 148 Slog.w(TAG, "Expect no certificate in signature"); 149 return false; 150 } 151 if (!signedData.getCRLs().getMatches(null).isEmpty()) { 152 Slog.w(TAG, "Expect no CRL in signature"); 153 return false; 154 } 155 156 X509Certificate trustedCert = (X509Certificate) CertificateFactory.getInstance("X.509") 157 .generateCertificate(derCertInputStream); 158 SignerInformationVerifier verifier = new JcaSimpleSignerInfoVerifierBuilder() 159 .build(trustedCert); 160 161 // Verify any signature with the trusted certificate. 162 for (SignerInformation si : signedData.getSignerInfos().getSigners()) { 163 // To be the most strict while dealing with the complicated PKCS#7 signature, reject 164 // everything we don't need. 165 if (si.getSignedAttributes() != null && si.getSignedAttributes().size() > 0) { 166 Slog.w(TAG, "Unexpected signed attributes"); 167 return false; 168 } 169 if (si.getUnsignedAttributes() != null && si.getUnsignedAttributes().size() > 0) { 170 Slog.w(TAG, "Unexpected unsigned attributes"); 171 return false; 172 } 173 if (!NISTObjectIdentifiers.id_sha256.getId().equals(si.getDigestAlgOID())) { 174 Slog.w(TAG, "Unsupported digest algorithm OID: " + si.getDigestAlgOID()); 175 return false; 176 } 177 if (!PKCSObjectIdentifiers.rsaEncryption.getId().equals(si.getEncryptionAlgOID())) { 178 Slog.w(TAG, "Unsupported encryption algorithm OID: " 179 + si.getEncryptionAlgOID()); 180 return false; 181 } 182 183 if (si.verify(verifier)) { 184 return true; 185 } 186 } 187 return false; 188 } catch (CertificateException | CMSException | OperatorCreationException e) { 189 Slog.w(TAG, "Error occurred during the PKCS#7 signature verification", e); 190 } 191 return false; 192 } 193 194 /** 195 * Returns fs-verity digest for the file if enabled, otherwise returns null. The digest is a 196 * hash of root hash of fs-verity's Merkle tree with extra metadata. 197 * 198 * @see <a href="https://www.kernel.org/doc/html/latest/filesystems/fsverity.html#file-digest-computation"> 199 * File digest computation in Linux kernel documentation</a> 200 * @return Bytes of fs-verity digest, or null if the file does not have fs-verity enabled 201 */ getFsverityDigest(@onNull String filePath)202 public static @Nullable byte[] getFsverityDigest(@NonNull String filePath) { 203 byte[] result = new byte[HASH_SIZE_BYTES]; 204 int retval = measureFsverityNative(filePath, result); 205 if (retval < 0) { 206 if (retval != -OsConstants.ENODATA) { 207 Slog.e(TAG, "Failed to measure fs-verity, errno " + -retval + ": " + filePath); 208 } 209 return null; 210 } 211 return result; 212 } 213 214 /** 215 * Generates an fs-verity digest from a V4Signature.HashingInfo and the file's size. 216 */ generateFsVerityDigest(long fileSize, @NonNull V4Signature.HashingInfo hashingInfo)217 public static @NonNull byte[] generateFsVerityDigest(long fileSize, 218 @NonNull V4Signature.HashingInfo hashingInfo) 219 throws DigestException, NoSuchAlgorithmException { 220 if (hashingInfo.rawRootHash == null || hashingInfo.rawRootHash.length != 32) { 221 throw new IllegalArgumentException("Expect a 32-byte rootHash for SHA256"); 222 } 223 if (hashingInfo.log2BlockSize != 12) { 224 throw new IllegalArgumentException( 225 "Unsupported log2BlockSize: " + hashingInfo.log2BlockSize); 226 } 227 228 var buffer = ByteBuffer.allocate(256); // sizeof(fsverity_descriptor) 229 buffer.order(ByteOrder.LITTLE_ENDIAN); 230 buffer.put((byte) 1); // version 231 buffer.put((byte) 1); // Merkle tree hash algorithm, 1 for SHA256 232 buffer.put(hashingInfo.log2BlockSize); // log2(block-size), only log2(4096) is supported 233 buffer.put((byte) 0); // size of salt in bytes; 0 if none 234 buffer.putInt(0); // reserved, must be 0 235 buffer.putLong(fileSize); // size of file the Merkle tree is built over 236 buffer.put(hashingInfo.rawRootHash); // Merkle tree root hash 237 // The rest are zeros, including the latter half of root hash unused for SHA256. 238 239 return MessageDigest.getInstance("SHA-256").digest(buffer.array()); 240 } 241 242 /** @hide */ 243 @VisibleForTesting toFormattedDigest(byte[] digest)244 public static byte[] toFormattedDigest(byte[] digest) { 245 // Construct fsverity_formatted_digest used in fs-verity's built-in signature verification. 246 ByteBuffer buffer = ByteBuffer.allocate(12 + digest.length); // struct size + sha256 size 247 buffer.order(ByteOrder.LITTLE_ENDIAN); 248 buffer.put("FSVerity".getBytes(StandardCharsets.US_ASCII)); 249 buffer.putShort((short) 1); // FS_VERITY_HASH_ALG_SHA256 250 buffer.putShort((short) digest.length); 251 buffer.put(digest); 252 return buffer.array(); 253 } 254 enableFsverityNative(@onNull String filePath)255 private static native int enableFsverityNative(@NonNull String filePath); enableFsverityForFdNative(int fd)256 private static native int enableFsverityForFdNative(int fd); measureFsverityNative(@onNull String filePath, @NonNull byte[] digest)257 private static native int measureFsverityNative(@NonNull String filePath, 258 @NonNull byte[] digest); statxForFsverityNative(@onNull String filePath)259 private static native int statxForFsverityNative(@NonNull String filePath); 260 } 261