• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright 2020 The gRPC Authors
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 io.grpc.binder;
18 
19 import android.annotation.SuppressLint;
20 import android.app.admin.DevicePolicyManager;
21 import android.content.Context;
22 import android.content.pm.PackageInfo;
23 import android.content.pm.PackageManager;
24 import android.content.pm.PackageManager.NameNotFoundException;
25 import android.content.pm.Signature;
26 import android.os.Build;
27 import android.os.Build.VERSION;
28 import android.os.Process;
29 import com.google.common.base.Preconditions;
30 import com.google.common.base.Predicate;
31 import com.google.common.collect.ImmutableList;
32 import com.google.common.collect.ImmutableSet;
33 import com.google.common.hash.Hashing;
34 import com.google.errorprone.annotations.CheckReturnValue;
35 import io.grpc.ExperimentalApi;
36 import io.grpc.Status;
37 import java.util.ArrayList;
38 import java.util.Arrays;
39 import java.util.Collection;
40 import java.util.Iterator;
41 import java.util.List;
42 
43 /** Static factory methods for creating standard security policies. */
44 @CheckReturnValue
45 public final class SecurityPolicies {
46 
47   private static final int MY_UID = Process.myUid();
48   private static final int SHA_256_BYTES_LENGTH = 32;
49 
SecurityPolicies()50   private SecurityPolicies() {}
51 
52   @ExperimentalApi("https://github.com/grpc/grpc-java/issues/8022")
serverInternalOnly()53   public static ServerSecurityPolicy serverInternalOnly() {
54     return new ServerSecurityPolicy();
55   }
56 
57   /**
58    * Creates a default {@link SecurityPolicy} that allows access only to callers with the same UID
59    * as the current process.
60    */
internalOnly()61   public static SecurityPolicy internalOnly() {
62     return new SecurityPolicy() {
63       @Override
64       public Status checkAuthorization(int uid) {
65         return uid == MY_UID
66             ? Status.OK
67             : Status.PERMISSION_DENIED.withDescription(
68                 "Rejected by (internal-only) security policy");
69       }
70     };
71   }
72 
73   @ExperimentalApi("https://github.com/grpc/grpc-java/issues/8022")
74   public static SecurityPolicy permissionDenied(String description) {
75     Status denied = Status.PERMISSION_DENIED.withDescription(description);
76     return new SecurityPolicy() {
77       @Override
78       public Status checkAuthorization(int uid) {
79         return denied;
80       }
81     };
82   }
83 
84   /**
85    * Creates a {@link SecurityPolicy} which checks if the package signature
86    * matches {@code requiredSignature}.
87    *
88    * @param packageName the package name of the allowed package.
89    * @param requiredSignature the allowed signature of the allowed package.
90    * @throws NullPointerException if any of the inputs are {@code null}.
91    */
92   @ExperimentalApi("https://github.com/grpc/grpc-java/issues/8022")
93   public static SecurityPolicy hasSignature(
94       PackageManager packageManager, String packageName, Signature requiredSignature) {
95     return oneOfSignatures(
96         packageManager, packageName, ImmutableList.of(requiredSignature));
97   }
98 
99   /**
100    * Creates {@link SecurityPolicy} which checks if the SHA-256 hash of the package signature
101    * matches {@code requiredSignatureSha256Hash}.
102    *
103    * @param packageName the package name of the allowed package.
104    * @param requiredSignatureSha256Hash the SHA-256 digest of the signature of the allowed package.
105    * @throws NullPointerException if any of the inputs are {@code null}.
106    * @throws IllegalArgumentException if {@code requiredSignatureSha256Hash} is not of length 32.
107    */
108   @ExperimentalApi("https://github.com/grpc/grpc-java/issues/8022")
109   public static SecurityPolicy hasSignatureSha256Hash(
110       PackageManager packageManager, String packageName, byte[] requiredSignatureSha256Hash) {
111     return oneOfSignatureSha256Hash(
112         packageManager, packageName, ImmutableList.of(requiredSignatureSha256Hash));
113   }
114 
115   /**
116    * Creates a {@link SecurityPolicy} which checks if the package signature
117    * matches any of {@code requiredSignatures}.
118    *
119    * @param packageName the package name of the allowed package.
120    * @param requiredSignatures the allowed signatures of the allowed package.
121    * @throws NullPointerException if any of the inputs are {@code null}.
122    * @throws IllegalArgumentException if {@code requiredSignatures} is empty.
123    */
124   @ExperimentalApi("https://github.com/grpc/grpc-java/issues/8022")
125   public static SecurityPolicy oneOfSignatures(
126       PackageManager packageManager,
127       String packageName,
128       Collection<Signature> requiredSignatures) {
129     Preconditions.checkNotNull(packageManager, "packageManager");
130     Preconditions.checkNotNull(packageName, "packageName");
131     Preconditions.checkNotNull(requiredSignatures, "requiredSignatures");
132     Preconditions.checkArgument(!requiredSignatures.isEmpty(),
133         "requiredSignatures");
134     ImmutableList<Signature> requiredSignaturesImmutable = ImmutableList.copyOf(requiredSignatures);
135 
136     for (Signature requiredSignature : requiredSignaturesImmutable) {
137       Preconditions.checkNotNull(requiredSignature);
138     }
139 
140     return new SecurityPolicy() {
141       @Override
142       public Status checkAuthorization(int uid) {
143         return checkUidSignature(
144             packageManager, uid, packageName, requiredSignaturesImmutable);
145       }
146     };
147   }
148 
149   /**
150    * Creates {@link SecurityPolicy} which checks if the SHA-256 hash of the package signature
151    * matches any of {@code requiredSignatureSha256Hashes}.
152    *
153    * @param packageName the package name of the allowed package.
154    * @param requiredSignatureSha256Hashes the SHA-256 digests of the signatures of the allowed
155    *     package.
156    * @throws NullPointerException if any of the inputs are {@code null}.
157    * @throws IllegalArgumentException if {@code requiredSignatureSha256Hashes} is empty, or if any
158    *     of the {@code requiredSignatureSha256Hashes} are not of length 32.
159    */
160   public static SecurityPolicy oneOfSignatureSha256Hash(
161       PackageManager packageManager,
162       String packageName,
163       List<byte[]> requiredSignatureSha256Hashes) {
164     Preconditions.checkNotNull(packageManager);
165     Preconditions.checkNotNull(packageName);
166     Preconditions.checkNotNull(requiredSignatureSha256Hashes);
167     Preconditions.checkArgument(!requiredSignatureSha256Hashes.isEmpty());
168 
169     ImmutableList.Builder<byte[]> immutableListBuilder = ImmutableList.builder();
170     for (byte[] requiredSignatureSha256Hash : requiredSignatureSha256Hashes) {
171       Preconditions.checkNotNull(requiredSignatureSha256Hash);
172       Preconditions.checkArgument(requiredSignatureSha256Hash.length == SHA_256_BYTES_LENGTH);
173       immutableListBuilder.add(
174           Arrays.copyOf(requiredSignatureSha256Hash, requiredSignatureSha256Hash.length));
175     }
176     ImmutableList<byte[]> requiredSignaturesHashesImmutable = immutableListBuilder.build();
177 
178     return new SecurityPolicy() {
179       @Override
180       public Status checkAuthorization(int uid) {
181         return checkUidSha256Signature(
182             packageManager, uid, packageName, requiredSignaturesHashesImmutable);
183       }
184     };
185   }
186 
187   /**
188    * Creates {@link SecurityPolicy} which checks if the app is a device owner app. See
189    * {@link DevicePolicyManager}.
190    */
191   public static SecurityPolicy isDeviceOwner(Context applicationContext) {
192     DevicePolicyManager devicePolicyManager =
193         (DevicePolicyManager) applicationContext.getSystemService(Context.DEVICE_POLICY_SERVICE);
194     return anyPackageWithUidSatisfies(
195         applicationContext,
196         pkg -> VERSION.SDK_INT >= 18 && devicePolicyManager.isDeviceOwnerApp(pkg),
197         "Rejected by device owner policy. No packages found for UID.",
198         "Rejected by device owner policy");
199   }
200 
201   /**
202    * Creates {@link SecurityPolicy} which checks if the app is a profile owner app. See
203    * {@link DevicePolicyManager}.
204    */
205   public static SecurityPolicy isProfileOwner(Context applicationContext) {
206     DevicePolicyManager devicePolicyManager =
207         (DevicePolicyManager) applicationContext.getSystemService(Context.DEVICE_POLICY_SERVICE);
208     return anyPackageWithUidSatisfies(
209         applicationContext,
210         pkg -> VERSION.SDK_INT >= 21 && devicePolicyManager.isProfileOwnerApp(pkg),
211         "Rejected by profile owner policy. No packages found for UID.",
212         "Rejected by profile owner policy");
213   }
214 
215   /**
216    * Creates {@link SecurityPolicy} which checks if the app is a profile owner app on an
217    * organization-owned device. See {@link DevicePolicyManager}.
218    */
219   public static SecurityPolicy isProfileOwnerOnOrganizationOwnedDevice(Context applicationContext) {
220     DevicePolicyManager devicePolicyManager =
221         (DevicePolicyManager) applicationContext.getSystemService(Context.DEVICE_POLICY_SERVICE);
222     return anyPackageWithUidSatisfies(
223         applicationContext,
224         pkg -> VERSION.SDK_INT >= 30
225             && devicePolicyManager.isProfileOwnerApp(pkg)
226             && devicePolicyManager.isOrganizationOwnedDeviceWithManagedProfile(),
227         "Rejected by profile owner on organization-owned device policy. No packages found for UID.",
228         "Rejected by profile owner on organization-owned device policy");
229   }
230 
231   private static Status checkUidSignature(
232       PackageManager packageManager,
233       int uid,
234       String packageName,
235       ImmutableList<Signature> requiredSignatures) {
236     String[] packages = packageManager.getPackagesForUid(uid);
237     if (packages == null) {
238       return Status.UNAUTHENTICATED.withDescription(
239           "Rejected by signature check security policy");
240     }
241     boolean packageNameMatched = false;
242     for (String pkg : packages) {
243       if (!packageName.equals(pkg)) {
244         continue;
245       }
246       packageNameMatched = true;
247       if (checkPackageSignature(packageManager, pkg, requiredSignatures::contains)) {
248         return Status.OK;
249       }
250     }
251     return Status.PERMISSION_DENIED.withDescription(
252         "Rejected by signature check security policy. Package name matched: "
253             + packageNameMatched);
254   }
255 
256   private static Status checkUidSha256Signature(
257       PackageManager packageManager,
258       int uid,
259       String packageName,
260       ImmutableList<byte[]> requiredSignatureSha256Hashes) {
261     String[] packages = packageManager.getPackagesForUid(uid);
262     if (packages == null) {
263       return Status.UNAUTHENTICATED.withDescription(
264           "Rejected by (SHA-256 hash signature check) security policy");
265     }
266     boolean packageNameMatched = false;
267     for (String pkg : packages) {
268       if (!packageName.equals(pkg)) {
269         continue;
270       }
271       packageNameMatched = true;
272       if (checkPackageSignature(
273           packageManager,
274           pkg,
275           (signature) ->
276               checkSignatureSha256HashesMatch(signature, requiredSignatureSha256Hashes))) {
277         return Status.OK;
278       }
279     }
280     return Status.PERMISSION_DENIED.withDescription(
281         "Rejected by (SHA-256 hash signature check) security policy. Package name matched: "
282             + packageNameMatched);
283   }
284 
285   /**
286    * Checks if the signature of {@code packageName} matches one of the given signatures.
287    *
288    * @param packageName the package to be checked
289    * @param signatureCheckFunction {@link Predicate} that takes a signature and verifies if it
290    * satisfies any signature constraints
291    * return {@code true} if {@code packageName} has a signature that satisfies {@code
292    * signatureCheckFunction}.
293    */
294   @SuppressWarnings("deprecation") // For PackageInfo.signatures
295   @SuppressLint("PackageManagerGetSignatures") // We only allow 1 signature.
296   private static boolean checkPackageSignature(
297       PackageManager packageManager,
298       String packageName,
299       Predicate<Signature> signatureCheckFunction) {
300     PackageInfo packageInfo;
301     try {
302       if (Build.VERSION.SDK_INT >= 28) {
303         packageInfo =
304             packageManager.getPackageInfo(packageName, PackageManager.GET_SIGNING_CERTIFICATES);
305         if (packageInfo.signingInfo == null) {
306           return false;
307         }
308         Signature[] signatures =
309             packageInfo.signingInfo.hasMultipleSigners()
310                 ? packageInfo.signingInfo.getApkContentsSigners()
311                 : packageInfo.signingInfo.getSigningCertificateHistory();
312 
313         for (Signature signature : signatures) {
314           if (signatureCheckFunction.apply(signature)) {
315             return true;
316           }
317         }
318       } else {
319         packageInfo = packageManager.getPackageInfo(packageName, PackageManager.GET_SIGNATURES);
320         if (packageInfo.signatures == null || packageInfo.signatures.length != 1) {
321           // Reject multiply-signed apks because of b/13678484
322           // (See PackageManagerGetSignatures supression above).
323           return false;
324         }
325 
326         if (signatureCheckFunction.apply(packageInfo.signatures[0])) {
327           return true;
328         }
329       }
330     } catch (NameNotFoundException nnfe) {
331       return false;
332     }
333     return false;
334   }
335 
336   /**
337    * Creates a {@link SecurityPolicy} that allows access if and only if *all* of the specified
338    * {@code securityPolicies} allow access.
339    *
340    * @param securityPolicies the security policies that all must allow access.
341    * @throws NullPointerException if any of the inputs are {@code null}.
342    * @throws IllegalArgumentException if {@code securityPolicies} is empty.
343    */
344   public static SecurityPolicy allOf(SecurityPolicy... securityPolicies) {
345     Preconditions.checkNotNull(securityPolicies, "securityPolicies");
346     Preconditions.checkArgument(securityPolicies.length > 0, "securityPolicies must not be empty");
347 
348     return allOfSecurityPolicy(securityPolicies);
349   }
350 
351   private static SecurityPolicy allOfSecurityPolicy(SecurityPolicy... securityPolicies) {
352     return new SecurityPolicy() {
353       @Override
354       public Status checkAuthorization(int uid) {
355         for (SecurityPolicy policy : securityPolicies) {
356           Status checkAuth = policy.checkAuthorization(uid);
357           if (!checkAuth.isOk()) {
358             return checkAuth;
359           }
360         }
361 
362         return Status.OK;
363       }
364     };
365   }
366 
367   /**
368    * Creates a {@link SecurityPolicy} that allows access if *any* of the specified {@code
369    * securityPolicies} allow access.
370    *
371    * <p>Policies will be checked in the order that they are passed. If a policy allows access,
372    * subsequent policies will not be checked.
373    *
374    * <p>If all policies deny access, the {@link io.grpc.Status} returned by {@code
375    * checkAuthorization} will included the concatenated descriptions of the failed policies and
376    * attach any additional causes as suppressed throwables. The status code will be that of the
377    * first failed policy.
378    *
379    * @param securityPolicies the security policies that will be checked.
380    * @throws NullPointerException if any of the inputs are {@code null}.
381    * @throws IllegalArgumentException if {@code securityPolicies} is empty.
382    */
383   public static SecurityPolicy anyOf(SecurityPolicy... securityPolicies) {
384     Preconditions.checkNotNull(securityPolicies, "securityPolicies");
385     Preconditions.checkArgument(securityPolicies.length > 0, "securityPolicies must not be empty");
386 
387     return anyOfSecurityPolicy(securityPolicies);
388   }
389 
390   private static SecurityPolicy anyOfSecurityPolicy(SecurityPolicy... securityPolicies) {
391     return new SecurityPolicy() {
392       @Override
393       public Status checkAuthorization(int uid) {
394         List<Status> failed = new ArrayList<>();
395         for (SecurityPolicy policy : securityPolicies) {
396           Status checkAuth = policy.checkAuthorization(uid);
397           if (checkAuth.isOk()) {
398             return checkAuth;
399           }
400           failed.add(checkAuth);
401         }
402 
403         Iterator<Status> iter = failed.iterator();
404         Status toReturn = iter.next();
405         while (iter.hasNext()) {
406           Status append = iter.next();
407           toReturn = toReturn.augmentDescription(append.getDescription());
408           if (append.getCause() != null) {
409             if (toReturn.getCause() != null) {
410               toReturn.getCause().addSuppressed(append.getCause());
411             } else {
412               toReturn = toReturn.withCause(append.getCause());
413             }
414           }
415         }
416         return toReturn;
417       }
418     };
419   }
420 
421   /**
422    * Creates a {@link SecurityPolicy} which checks if the caller has all of the given permissions
423    * from {@code permissions}.
424    *
425    * @param permissions all permissions that the calling package needs to have
426    * @throws NullPointerException if any of the inputs are {@code null}
427    * @throws IllegalArgumentException if {@code permissions} is empty
428    */
429   public static SecurityPolicy hasPermissions(
430       PackageManager packageManager, ImmutableSet<String> permissions) {
431     Preconditions.checkNotNull(packageManager, "packageManager");
432     Preconditions.checkNotNull(permissions, "permissions");
433     Preconditions.checkArgument(!permissions.isEmpty(), "permissions");
434     return new SecurityPolicy() {
435       @Override
436       public Status checkAuthorization(int uid) {
437         return checkPermissions(uid, packageManager, permissions);
438       }
439     };
440   }
441 
442   private static Status checkPermissions(
443       int uid, PackageManager packageManager, ImmutableSet<String> permissions) {
444     String[] packages = packageManager.getPackagesForUid(uid);
445     if (packages == null || packages.length == 0) {
446       return Status.UNAUTHENTICATED.withDescription(
447           "Rejected by permission check security policy. No packages found for uid");
448     }
449     for (String pkg : packages) {
450       for (String permission : permissions) {
451         if (packageManager.checkPermission(permission, pkg) != PackageManager.PERMISSION_GRANTED) {
452           return Status.PERMISSION_DENIED.withDescription(
453               "Rejected by permission check security policy. "
454                   + pkg
455                   + " does not have permission "
456                   + permission);
457         }
458       }
459     }
460 
461     return Status.OK;
462   }
463 
464   private static SecurityPolicy anyPackageWithUidSatisfies(
465       Context applicationContext,
466       Predicate<String> condition,
467       String errorMessageForNoPackages,
468       String errorMessageForDenied) {
469     return new SecurityPolicy() {
470       @Override
471       public Status checkAuthorization(int uid) {
472         String[] packages = applicationContext.getPackageManager().getPackagesForUid(uid);
473         if (packages == null || packages.length == 0) {
474           return Status.UNAUTHENTICATED.withDescription(errorMessageForNoPackages);
475         }
476 
477         for (String pkg : packages) {
478           if (condition.apply(pkg)) {
479             return Status.OK;
480           }
481         }
482         return Status.PERMISSION_DENIED.withDescription(errorMessageForDenied);
483       }
484     };
485   }
486 
487   /**
488    * Checks if the SHA-256 hash of the {@code signature} matches one of the {@code
489    * expectedSignatureSha256Hashes}.
490    */
491   private static boolean checkSignatureSha256HashesMatch(
492       Signature signature, List<byte[]> expectedSignatureSha256Hashes) {
493     byte[] signatureHash = getSha256Hash(signature);
494     for (byte[] hash : expectedSignatureSha256Hashes) {
495       if (Arrays.equals(hash, signatureHash)) {
496         return true;
497       }
498     }
499     return false;
500   }
501 
502   /** Returns SHA-256 hash of the provided signature. */
503   private static byte[] getSha256Hash(Signature signature) {
504     return Hashing.sha256().hashBytes(signature.toByteArray()).asBytes();
505   }
506 }
507