Skip to content

Commit

Permalink
Merge branch 'grpc:master' into okhttp-additional-ciphers
Browse files Browse the repository at this point in the history
  • Loading branch information
beatrausch committed Nov 3, 2021
2 parents a8b2911 + 0000cba commit 98c5d32
Show file tree
Hide file tree
Showing 24 changed files with 1,583 additions and 303 deletions.
Expand Up @@ -36,11 +36,13 @@
import io.grpc.ManagedChannel;
import io.grpc.Metadata;
import io.grpc.MethodDescriptor;
import io.grpc.NameResolverRegistry;
import io.grpc.Server;
import io.grpc.ServerCallHandler;
import io.grpc.ServerInterceptors;
import io.grpc.ServerServiceDefinition;
import io.grpc.internal.GrpcUtil;
import io.grpc.internal.testing.FakeNameResolverProvider;
import io.grpc.stub.ClientCalls;
import io.grpc.stub.ServerCalls;
import io.grpc.stub.StreamObserver;
Expand All @@ -66,6 +68,7 @@ public final class BinderChannelSmokeTest {

private static final int SLIGHTLY_MORE_THAN_ONE_BLOCK = 16 * 1024 + 100;
private static final String MSG = "Some text which will be repeated many many times";
private static final String SERVER_TARGET_URI = "fake://server";

final MethodDescriptor<String, String> method =
MethodDescriptor.newBuilder(StringMarshaller.INSTANCE, StringMarshaller.INSTANCE)
Expand All @@ -85,7 +88,7 @@ public final class BinderChannelSmokeTest {
.setType(MethodDescriptor.MethodType.BIDI_STREAMING)
.build();

AndroidComponentAddress serverAddress;
FakeNameResolverProvider fakeNameResolverProvider;
ManagedChannel channel;
AtomicReference<Metadata> headersCapture = new AtomicReference<>();

Expand Down Expand Up @@ -118,6 +121,8 @@ public void setUp() throws Exception {
TestUtils.recordRequestHeadersInterceptor(headersCapture));

AndroidComponentAddress serverAddress = HostServices.allocateService(appContext);
fakeNameResolverProvider = new FakeNameResolverProvider(SERVER_TARGET_URI, serverAddress);
NameResolverRegistry.getDefaultRegistry().register(fakeNameResolverProvider);
HostServices.configureService(serverAddress,
HostServices.serviceParamsBuilder()
.setServerFactory((service, receiver) ->
Expand All @@ -132,6 +137,7 @@ public void setUp() throws Exception {
@After
public void tearDown() throws Exception {
channel.shutdownNow();
NameResolverRegistry.getDefaultRegistry().deregister(fakeNameResolverProvider);
HostServices.awaitServiceShutdown();
}

Expand Down Expand Up @@ -192,6 +198,12 @@ public void testStreamingCallOptionHeaders() throws Exception {
assertThat(headersCapture.get().get(GrpcUtil.TIMEOUT_KEY)).isGreaterThan(0);
}

@Test
public void testConnectViaTargetUri() throws Exception {
channel = BinderChannelBuilder.forTarget(SERVER_TARGET_URI, appContext).build();
assertThat(doCall("Hello").get()).isEqualTo("Hello");
}

private static String createLargeString(int size) {
StringBuilder sb = new StringBuilder();
while (sb.length() < size) {
Expand Down
56 changes: 44 additions & 12 deletions binder/src/main/java/io/grpc/binder/BinderChannelBuilder.java
Expand Up @@ -67,13 +67,35 @@ public final class BinderChannelBuilder
* <p>You the caller are responsible for managing the lifecycle of any channels built by the
* resulting builder. They will not be shut down automatically.
*
* @param targetAddress the {@link AndroidComponentAddress} referencing the service to bind to.
* @param directAddress the {@link AndroidComponentAddress} referencing the service to bind to.
* @param sourceContext the context to bind from (e.g. The current Activity or Application).
* @return a new builder
*/
public static BinderChannelBuilder forAddress(
AndroidComponentAddress targetAddress, Context sourceContext) {
return new BinderChannelBuilder(targetAddress, sourceContext);
AndroidComponentAddress directAddress, Context sourceContext) {
return new BinderChannelBuilder(
checkNotNull(directAddress, "directAddress"), null, sourceContext);
}

/**
* Creates a channel builder that will bind to a remote Android service, via a string
* target name which will be resolved.
*
* <p>The underlying Android binding will be torn down when the channel becomes idle. This happens
* after 30 minutes without use by default but can be configured via {@link
* ManagedChannelBuilder#idleTimeout(long, TimeUnit)} or triggered manually with {@link
* ManagedChannel#enterIdle()}.
*
* <p>You the caller are responsible for managing the lifecycle of any channels built by the
* resulting builder. They will not be shut down automatically.
*
* @param target A target uri which should resolve into an {@link AndroidComponentAddress}
* referencing the service to bind to.
* @param sourceContext the context to bind from (e.g. The current Activity or Application).
* @return a new builder
*/
public static BinderChannelBuilder forTarget(String target, Context sourceContext) {
return new BinderChannelBuilder(null, checkNotNull(target, "target"), sourceContext);
}

/**
Expand All @@ -88,7 +110,7 @@ public static BinderChannelBuilder forAddress(String name, int port) {
/**
* Always fails. Call {@link #forAddress(AndroidComponentAddress, Context)} instead.
*/
@DoNotCall("Unsupported. Use forAddress(AndroidComponentAddress, Context) instead")
@DoNotCall("Unsupported. Use forTarget(String, Context) instead")
public static BinderChannelBuilder forTarget(String target) {
throw new UnsupportedOperationException(
"call forAddress(AndroidComponentAddress, Context) instead");
Expand All @@ -104,9 +126,11 @@ public static BinderChannelBuilder forTarget(String target) {
private BindServiceFlags bindServiceFlags;

private BinderChannelBuilder(
AndroidComponentAddress targetAddress,
@Nullable AndroidComponentAddress directAddress,
@Nullable String target,
Context sourceContext) {
mainThreadExecutor = ContextCompat.getMainExecutor(sourceContext);
mainThreadExecutor =
ContextCompat.getMainExecutor(checkNotNull(sourceContext, "sourceContext"));
securityPolicy = SecurityPolicies.internalOnly();
inboundParcelablePolicy = InboundParcelablePolicy.DEFAULT;
bindServiceFlags = BindServiceFlags.DEFAULTS;
Expand All @@ -126,12 +150,20 @@ public ClientTransportFactory buildClientTransportFactory() {
}
}

managedChannelImplBuilder =
new ManagedChannelImplBuilder(
targetAddress,
targetAddress.getAuthority(),
new BinderChannelTransportFactoryBuilder(),
null);
if (directAddress != null) {
managedChannelImplBuilder =
new ManagedChannelImplBuilder(
directAddress,
directAddress.getAuthority(),
new BinderChannelTransportFactoryBuilder(),
null);
} else {
managedChannelImplBuilder =
new ManagedChannelImplBuilder(
target,
new BinderChannelTransportFactoryBuilder(),
null);
}
}

@Override
Expand Down
132 changes: 132 additions & 0 deletions binder/src/main/java/io/grpc/binder/SecurityPolicies.java
Expand Up @@ -16,9 +16,20 @@

package io.grpc.binder;

import android.annotation.SuppressLint;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
import android.content.pm.PackageManager.NameNotFoundException;
import android.content.pm.Signature;
import android.os.Build;
import android.os.Process;
import com.google.common.base.Preconditions;
import com.google.common.collect.ImmutableList;
import io.grpc.ExperimentalApi;
import io.grpc.Status;
import java.util.Arrays;
import java.util.Collection;
import java.util.List;
import javax.annotation.CheckReturnValue;

/** Static factory methods for creating standard security policies. */
Expand Down Expand Up @@ -55,4 +66,125 @@ public Status checkAuthorization(int uid) {
}
};
}

/**
* Creates a {@link SecurityPolicy} which checks if the package signature
* matches {@code requiredSignature}.
*
* @param packageName the package name of the allowed package.
* @param requiredSignature the allowed signature of the allowed package.
* @throws NullPointerException if any of the inputs are {@code null}.
*/
public static SecurityPolicy hasSignature(
PackageManager packageManager, String packageName, Signature requiredSignature) {
return oneOfSignatures(
packageManager, packageName, ImmutableList.of(requiredSignature));
}

/**
* Creates a {@link SecurityPolicy} which checks if the package signature
* matches any of {@code requiredSignatures}.
*
* @param packageName the package name of the allowed package.
* @param requiredSignatures the allowed signatures of the allowed package.
* @throws NullPointerException if any of the inputs are {@code null}.
* @throws IllegalArgumentException if {@code requiredSignatures} is empty.
*/
public static SecurityPolicy oneOfSignatures(
PackageManager packageManager,
String packageName,
Collection<Signature> requiredSignatures) {
Preconditions.checkNotNull(packageManager, "packageManager");
Preconditions.checkNotNull(packageName, "packageName");
Preconditions.checkNotNull(requiredSignatures, "requiredSignatures");
Preconditions.checkArgument(!requiredSignatures.isEmpty(),
"requiredSignatures");
ImmutableList<Signature> requiredSignaturesImmutable = ImmutableList.copyOf(requiredSignatures);

for (Signature requiredSignature : requiredSignaturesImmutable) {
Preconditions.checkNotNull(requiredSignature);
}

return new SecurityPolicy() {
@Override
public Status checkAuthorization(int uid) {
return checkUidSignature(
packageManager, uid, packageName, requiredSignaturesImmutable);
}
};
}

private static Status checkUidSignature(
PackageManager packageManager,
int uid,
String packageName,
ImmutableList<Signature> requiredSignatures) {
String[] packages = packageManager.getPackagesForUid(uid);
if (packages == null) {
return Status.UNAUTHENTICATED.withDescription(
"Rejected by signature check security policy");
}
boolean packageNameMatched = false;
for (String pkg : packages) {
if (!packageName.equals(pkg)) {
continue;
}
packageNameMatched = true;
if (checkPackageSignature(packageManager, pkg, requiredSignatures)) {
return Status.OK;
}
}
return Status.PERMISSION_DENIED.withDescription(
"Rejected by signature check security policy. Package name matched: "
+ packageNameMatched);
}

/**
* Checks if the signature of {@code packageName} matches one of the given signatures.
*
* @param packageName the package to be checked
* @param requiredSignatures list of signatures.
* @return {@code true} if {@code packageName} has a matching signature.
*/
@SuppressWarnings("deprecation") // For PackageInfo.signatures
@SuppressLint("PackageManagerGetSignatures") // We only allow 1 signature.
private static boolean checkPackageSignature(
PackageManager packageManager,
String packageName,
ImmutableList<Signature> requiredSignatures) {
PackageInfo packageInfo;
try {
if (Build.VERSION.SDK_INT >= 28) {
packageInfo =
packageManager.getPackageInfo(packageName, PackageManager.GET_SIGNING_CERTIFICATES);
if (packageInfo.signingInfo == null) {
return false;
}
Signature[] signatures =
packageInfo.signingInfo.hasMultipleSigners()
? packageInfo.signingInfo.getApkContentsSigners()
: packageInfo.signingInfo.getSigningCertificateHistory();

for (Signature signature : signatures) {
if (requiredSignatures.contains(signature)) {
return true;
}
}
} else {
packageInfo = packageManager.getPackageInfo(packageName, PackageManager.GET_SIGNATURES);
if (packageInfo.signatures == null || packageInfo.signatures.length != 1) {
// Reject multiply-signed apks because of b/13678484
// (See PackageManagerGetSignatures supression above).
return false;
}

if (requiredSignatures.contains(packageInfo.signatures[0])) {
return true;
}
}
} catch (NameNotFoundException nnfe) {
return false;
}
return false;
}
}
5 changes: 5 additions & 0 deletions binder/src/main/java/io/grpc/binder/SecurityPolicy.java
Expand Up @@ -23,6 +23,11 @@
/**
* Decides whether a given Android UID is authorized to access some resource.
*
* While it's possible to extend this class to define your own policy, it's strongly
* recommended that you only use the policies provided by the {@link SecurityPolicies} or
* {@link UntrustedSecurityPolicies} classes. Implementing your own security policy requires
* significant care, and an understanding of the details and pitfalls of Android security.
*
* <p><b>IMPORTANT</b> For any concrete extensions of this class, it's assumed that the
* authorization status of a given UID will <b>not</b> change as long as a process with that UID is
* alive.
Expand Down
47 changes: 47 additions & 0 deletions binder/src/main/java/io/grpc/binder/UntrustedSecurityPolicies.java
@@ -0,0 +1,47 @@
/*
* Copyright 2021 The gRPC Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package io.grpc.binder;

import io.grpc.ExperimentalApi;
import io.grpc.Status;
import javax.annotation.CheckReturnValue;

/**
* Static factory methods for creating untrusted security policies.
*/
@CheckReturnValue
@ExperimentalApi("https://github.com/grpc/grpc-java/issues/8022")
public final class UntrustedSecurityPolicies {

private UntrustedSecurityPolicies() {}

/**
* Return a security policy which allows any peer on device.
* Servers should only use this policy if they intend to expose
* a service to all applications on device.
* Clients should only use this policy if they don't need to trust the
* application they're connecting to.
*/
public static SecurityPolicy untrustedPublic() {
return new SecurityPolicy() {
@Override
public Status checkAuthorization(int uid) {
return Status.OK;
}
};
}
}

0 comments on commit 98c5d32

Please sign in to comment.