• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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