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.os.Build; 21 import android.os.SystemProperties; 22 import android.system.Os; 23 import android.system.OsConstants; 24 import android.util.Slog; 25 26 import com.android.internal.annotations.VisibleForTesting; 27 import com.android.internal.org.bouncycastle.asn1.nist.NISTObjectIdentifiers; 28 import com.android.internal.org.bouncycastle.asn1.pkcs.PKCSObjectIdentifiers; 29 import com.android.internal.org.bouncycastle.cms.CMSException; 30 import com.android.internal.org.bouncycastle.cms.CMSProcessableByteArray; 31 import com.android.internal.org.bouncycastle.cms.CMSSignedData; 32 import com.android.internal.org.bouncycastle.cms.SignerInformation; 33 import com.android.internal.org.bouncycastle.cms.SignerInformationVerifier; 34 import com.android.internal.org.bouncycastle.cms.jcajce.JcaSimpleSignerInfoVerifierBuilder; 35 import com.android.internal.org.bouncycastle.operator.OperatorCreationException; 36 37 import java.io.File; 38 import java.io.IOException; 39 import java.io.InputStream; 40 import java.nio.ByteBuffer; 41 import java.nio.ByteOrder; 42 import java.nio.charset.StandardCharsets; 43 import java.security.cert.CertificateException; 44 import java.security.cert.CertificateFactory; 45 import java.security.cert.X509Certificate; 46 47 /** Provides fsverity related operations. */ 48 public abstract class VerityUtils { 49 private static final String TAG = "VerityUtils"; 50 51 /** 52 * File extension of the signature file. For example, foo.apk.fsv_sig is the signature file of 53 * foo.apk. 54 */ 55 public static final String FSVERITY_SIGNATURE_FILE_EXTENSION = ".fsv_sig"; 56 57 /** SHA256 hash size. */ 58 private static final int HASH_SIZE_BYTES = 32; 59 isFsVeritySupported()60 public static boolean isFsVeritySupported() { 61 return Build.VERSION.DEVICE_INITIAL_SDK_INT >= Build.VERSION_CODES.R 62 || SystemProperties.getInt("ro.apk_verity.mode", 0) == 2; 63 } 64 65 /** Returns true if the given file looks like containing an fs-verity signature. */ isFsveritySignatureFile(File file)66 public static boolean isFsveritySignatureFile(File file) { 67 return file.getName().endsWith(FSVERITY_SIGNATURE_FILE_EXTENSION); 68 } 69 70 /** Returns the fs-verity signature file path of the given file. */ getFsveritySignatureFilePath(String filePath)71 public static String getFsveritySignatureFilePath(String filePath) { 72 return filePath + FSVERITY_SIGNATURE_FILE_EXTENSION; 73 } 74 75 /** Enables fs-verity for the file without signature. */ setUpFsverity(@onNull String filePath)76 public static void setUpFsverity(@NonNull String filePath) throws IOException { 77 int errno = enableFsverityNative(filePath); 78 if (errno != 0) { 79 throw new IOException("Failed to enable fs-verity on " + filePath + ": " 80 + Os.strerror(errno)); 81 } 82 } 83 84 /** Enables fs-verity for an open file without signature. */ setUpFsverity(int fd)85 public static void setUpFsverity(int fd) throws IOException { 86 int errno = enableFsverityForFdNative(fd); 87 if (errno != 0) { 88 throw new IOException("Failed to enable fs-verity on FD(" + fd + "): " 89 + Os.strerror(errno)); 90 } 91 } 92 93 /** Returns whether the file has fs-verity enabled. */ hasFsverity(@onNull String filePath)94 public static boolean hasFsverity(@NonNull String filePath) { 95 int retval = statxForFsverityNative(filePath); 96 if (retval < 0) { 97 Slog.e(TAG, "Failed to check whether fs-verity is enabled, errno " + -retval + ": " 98 + filePath); 99 return false; 100 } 101 return (retval == 1); 102 } 103 104 /** 105 * Verifies the signature over the fs-verity digest using the provided certificate. 106 * 107 * This method should only be used by any existing fs-verity use cases that require 108 * PKCS#7 signature verification, if backward compatibility is necessary. 109 * 110 * Since PKCS#7 is too flexible, for the current specific need, only specific configuration 111 * will be accepted: 112 * <ul> 113 * <li>Must use SHA256 as the digest algorithm 114 * <li>Must use rsaEncryption as signature algorithm 115 * <li>Must be detached / without content 116 * <li>Must not include any signed or unsigned attributes 117 * </ul> 118 * 119 * It is up to the caller to provide an appropriate/trusted certificate. 120 * 121 * @param signatureBlock byte array of a PKCS#7 detached signature 122 * @param digest fs-verity digest with the common configuration using sha256 123 * @param derCertInputStream an input stream of a X.509 certificate in DER 124 * @return whether the verification succeeds 125 */ verifyPkcs7DetachedSignature(@onNull byte[] signatureBlock, @NonNull byte[] digest, @NonNull InputStream derCertInputStream)126 public static boolean verifyPkcs7DetachedSignature(@NonNull byte[] signatureBlock, 127 @NonNull byte[] digest, @NonNull InputStream derCertInputStream) { 128 if (digest.length != 32) { 129 Slog.w(TAG, "Only sha256 is currently supported"); 130 return false; 131 } 132 133 try { 134 CMSSignedData signedData = new CMSSignedData( 135 new CMSProcessableByteArray(toFormattedDigest(digest)), 136 signatureBlock); 137 138 if (!signedData.isDetachedSignature()) { 139 Slog.w(TAG, "Expect only detached siganture"); 140 return false; 141 } 142 if (!signedData.getCertificates().getMatches(null).isEmpty()) { 143 Slog.w(TAG, "Expect no certificate in signature"); 144 return false; 145 } 146 if (!signedData.getCRLs().getMatches(null).isEmpty()) { 147 Slog.w(TAG, "Expect no CRL in signature"); 148 return false; 149 } 150 151 X509Certificate trustedCert = (X509Certificate) CertificateFactory.getInstance("X.509") 152 .generateCertificate(derCertInputStream); 153 SignerInformationVerifier verifier = new JcaSimpleSignerInfoVerifierBuilder() 154 .build(trustedCert); 155 156 // Verify any signature with the trusted certificate. 157 for (SignerInformation si : signedData.getSignerInfos().getSigners()) { 158 // To be the most strict while dealing with the complicated PKCS#7 signature, reject 159 // everything we don't need. 160 if (si.getSignedAttributes() != null && si.getSignedAttributes().size() > 0) { 161 Slog.w(TAG, "Unexpected signed attributes"); 162 return false; 163 } 164 if (si.getUnsignedAttributes() != null && si.getUnsignedAttributes().size() > 0) { 165 Slog.w(TAG, "Unexpected unsigned attributes"); 166 return false; 167 } 168 if (!NISTObjectIdentifiers.id_sha256.getId().equals(si.getDigestAlgOID())) { 169 Slog.w(TAG, "Unsupported digest algorithm OID: " + si.getDigestAlgOID()); 170 return false; 171 } 172 if (!PKCSObjectIdentifiers.rsaEncryption.getId().equals(si.getEncryptionAlgOID())) { 173 Slog.w(TAG, "Unsupported encryption algorithm OID: " 174 + si.getEncryptionAlgOID()); 175 return false; 176 } 177 178 if (si.verify(verifier)) { 179 return true; 180 } 181 } 182 return false; 183 } catch (CertificateException | CMSException | OperatorCreationException e) { 184 Slog.w(TAG, "Error occurred during the PKCS#7 signature verification", e); 185 } 186 return false; 187 } 188 189 /** 190 * Returns fs-verity digest for the file if enabled, otherwise returns null. The digest is a 191 * hash of root hash of fs-verity's Merkle tree with extra metadata. 192 * 193 * @see <a href="https://www.kernel.org/doc/html/latest/filesystems/fsverity.html#file-digest-computation"> 194 * File digest computation in Linux kernel documentation</a> 195 * @return Bytes of fs-verity digest 196 */ getFsverityDigest(@onNull String filePath)197 public static byte[] getFsverityDigest(@NonNull String filePath) { 198 byte[] result = new byte[HASH_SIZE_BYTES]; 199 int retval = measureFsverityNative(filePath, result); 200 if (retval < 0) { 201 if (retval != -OsConstants.ENODATA) { 202 Slog.e(TAG, "Failed to measure fs-verity, errno " + -retval + ": " + filePath); 203 } 204 return null; 205 } 206 return result; 207 } 208 209 /** @hide */ 210 @VisibleForTesting toFormattedDigest(byte[] digest)211 public static byte[] toFormattedDigest(byte[] digest) { 212 // Construct fsverity_formatted_digest used in fs-verity's built-in signature verification. 213 ByteBuffer buffer = ByteBuffer.allocate(12 + digest.length); // struct size + sha256 size 214 buffer.order(ByteOrder.LITTLE_ENDIAN); 215 buffer.put("FSVerity".getBytes(StandardCharsets.US_ASCII)); 216 buffer.putShort((short) 1); // FS_VERITY_HASH_ALG_SHA256 217 buffer.putShort((short) digest.length); 218 buffer.put(digest); 219 return buffer.array(); 220 } 221 enableFsverityNative(@onNull String filePath)222 private static native int enableFsverityNative(@NonNull String filePath); enableFsverityForFdNative(int fd)223 private static native int enableFsverityForFdNative(int fd); measureFsverityNative(@onNull String filePath, @NonNull byte[] digest)224 private static native int measureFsverityNative(@NonNull String filePath, 225 @NonNull byte[] digest); statxForFsverityNative(@onNull String filePath)226 private static native int statxForFsverityNative(@NonNull String filePath); 227 } 228