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