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