Skip to content

Commit

Permalink
alts: Use Conscrypt when available
Browse files Browse the repository at this point in the history
We depend on Conscrypt to help ensure Conscrypt 2.1.0 or newer is used.
It's not 100% clear this is the best approach, but it is the simplest at
present. If Conscrypt is not available then we will just use the JDK's
slower implementation of AES-GCM.

Fixes grpc#6213
  • Loading branch information
ejona86 authored Sep 30, 2019
1 parent a8137dc commit 0be86a5
Show file tree
Hide file tree
Showing 6 changed files with 89 additions and 15 deletions.
6 changes: 3 additions & 3 deletions alts/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,8 @@ dependencies {
project(':grpc-protobuf'),
project(':grpc-stub'),
libraries.lang,
libraries.protobuf
libraries.protobuf,
libraries.conscrypt
compile (libraries.google_auth_oauth2_http) {
// prefer 26.0-android from libraries instead of 25.1-android
exclude group: 'com.google.guava', module: 'guava'
Expand All @@ -36,8 +37,7 @@ dependencies {
libraries.mockito,
libraries.truth
testRuntime libraries.netty_tcnative,
libraries.netty_epoll,
libraries.conscrypt
libraries.netty_epoll
signature 'org.codehaus.mojo.signature:java17:1.0@signature'
}

Expand Down
62 changes: 61 additions & 1 deletion alts/src/main/java/io/grpc/alts/internal/AesGcmAeadCrypter.java
Original file line number Diff line number Diff line change
Expand Up @@ -18,29 +18,40 @@

import static com.google.common.base.Preconditions.checkArgument;

import java.lang.reflect.Method;
import java.nio.ByteBuffer;
import java.security.GeneralSecurityException;
import java.security.Provider;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.annotation.Nullable;
import javax.crypto.Cipher;
import javax.crypto.spec.GCMParameterSpec;
import javax.crypto.spec.SecretKeySpec;

/** AES128-GCM implementation of {@link AeadCrypter} that uses default JCE provider. */
final class AesGcmAeadCrypter implements AeadCrypter {
private static final Logger logger = Logger.getLogger(AesGcmAeadCrypter.class.getName());
private static final int KEY_LENGTH = 16;
private static final int TAG_LENGTH = 16;
static final int NONCE_LENGTH = 12;

private static final String AES = "AES";
private static final String AES_GCM = AES + "/GCM/NoPadding";
// Conscrypt if available, otherwise null. Conscrypt is much faster than Java 8's JSSE
private static final Provider CONSCRYPT = getConscrypt();

private final byte[] key;
private final Cipher cipher;

AesGcmAeadCrypter(byte[] key) throws GeneralSecurityException {
checkArgument(key.length == KEY_LENGTH);
this.key = key;
cipher = Cipher.getInstance(AES_GCM);
if (CONSCRYPT != null) {
cipher = Cipher.getInstance(AES_GCM, CONSCRYPT);
} else {
cipher = Cipher.getInstance(AES_GCM);
}
}

private int encryptAad(
Expand Down Expand Up @@ -98,4 +109,53 @@ public void decrypt(ByteBuffer plaintext, ByteBuffer ciphertext, ByteBuffer aad,
static int getKeyLength() {
return KEY_LENGTH;
}

private static Provider getConscrypt() {
// This is equivalent to if (Conscrypt.isAvailable()) return Conscrypt.newProvider();

// Conscrypt 2.1.0 or later is required. If an older version is used, it will fail with these
// sorts of errors:
// "The underlying Cipher implementation does not support this method"
// "error:1e000067:Cipher functions:OPENSSL_internal:BUFFER_TOO_SMALL"
//
// While we could use Conscrypt.version() to check compatibility, that is _very_ verbose via
// reflection. In practice, old conscrypts are probably not much of a problem.
Class<?> conscryptClass;
try {
conscryptClass = Class.forName("org.conscrypt.Conscrypt");
} catch (ClassNotFoundException ex) {
logger.log(Level.FINE, "Could not find Conscrypt", ex);
return null;
}
Method method;
try {
method = conscryptClass.getMethod("newProvider");
} catch (SecurityException ex) {
logger.log(Level.FINE, "Could not find Conscrypt factory method", ex);
return null;
} catch (NoSuchMethodException ex) {
logger.log(Level.WARNING, "Could not find Conscrypt factory method", ex);
return null;
}
Object provider;
try {
provider = method.invoke(null);
} catch (IllegalAccessException ex) {
logger.log(Level.WARNING, "Could not call Conscrypt factory method", ex);
return null;
} catch (Throwable ex) {
// This is probably an InvocationTargetException, which means something's wrong with the JNI
// loading. Maybe the platform is not supported. We could have used Conscrypt.isAvailable(),
// but it just catches Throwable as well
logger.log(Level.WARNING, "Failed calling Conscrypt factory method", ex);
return null;
}
if (!(provider instanceof Provider)) {
logger.log(
Level.WARNING, "Could not load Conscrypt. Returned provider was not a Provider: {0}",
provider.getClass().getName());
return null;
}
return (Provider) provider;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@

/** Abstract class for unit tests of {@link ChannelCrypterNetty}. */
public abstract class ChannelCrypterNettyTestBase {
private static final String DECRYPTION_FAILURE_MESSAGE = "Tag mismatch";
private static final String DECRYPTION_FAILURE_MESSAGE_RE = "Tag mismatch|BAD_DECRYPT";

protected final List<ReferenceCounted> references = new ArrayList<>();
public ChannelCrypterNetty client;
Expand Down Expand Up @@ -169,7 +169,7 @@ public void reflection() throws GeneralSecurityException {
client.decrypt(frameDecrypt.out, frameDecrypt.tag, frameDecrypt.ciphertext);
fail("Exception expected");
} catch (AEADBadTagException ex) {
assertThat(ex).hasMessageThat().contains(DECRYPTION_FAILURE_MESSAGE);
assertThat(ex).hasMessageThat().containsMatch(DECRYPTION_FAILURE_MESSAGE_RE);
}
}

Expand All @@ -186,7 +186,7 @@ public void skipMessage() throws GeneralSecurityException {
client.decrypt(frameDecrypt.out, frameDecrypt.tag, frameDecrypt.ciphertext);
fail("Exception expected");
} catch (AEADBadTagException ex) {
assertThat(ex).hasMessageThat().contains(DECRYPTION_FAILURE_MESSAGE);
assertThat(ex).hasMessageThat().containsMatch(DECRYPTION_FAILURE_MESSAGE_RE);
}
}

Expand All @@ -202,7 +202,7 @@ public void corruptMessage() throws GeneralSecurityException {
client.decrypt(frameDecrypt.out, frameDecrypt.tag, frameDecrypt.ciphertext);
fail("Exception expected");
} catch (AEADBadTagException ex) {
assertThat(ex).hasMessageThat().contains(DECRYPTION_FAILURE_MESSAGE);
assertThat(ex).hasMessageThat().containsMatch(DECRYPTION_FAILURE_MESSAGE_RE);
}
}

Expand All @@ -220,7 +220,7 @@ public void replayMessage() throws GeneralSecurityException {
server.decrypt(frameDecrypt2.out, frameDecrypt2.tag, frameDecrypt2.ciphertext);
fail("Exception expected");
} catch (AEADBadTagException ex) {
assertThat(ex).hasMessageThat().contains(DECRYPTION_FAILURE_MESSAGE);
assertThat(ex).hasMessageThat().containsMatch(DECRYPTION_FAILURE_MESSAGE_RE);
}
}
}
10 changes: 5 additions & 5 deletions alts/src/test/java/io/grpc/alts/internal/TsiTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@

/** Utility class that provides tests for implementations of @{link TsiHandshaker}. */
public final class TsiTest {
private static final String DECRYPTION_FAILURE_RE = "Tag mismatch!";
private static final String DECRYPTION_FAILURE_RE = "Tag mismatch!|BAD_DECRYPT";

private TsiTest() {}

Expand Down Expand Up @@ -282,7 +282,7 @@ public void accept(ByteBuf buf) {
receiver.unprotect(protect, unprotectOut, alloc);
fail("Exception expected");
} catch (AEADBadTagException ex) {
assertThat(ex).hasMessageThat().contains(DECRYPTION_FAILURE_RE);
assertThat(ex).hasMessageThat().containsMatch(DECRYPTION_FAILURE_RE);
}

sender.destroy();
Expand Down Expand Up @@ -321,7 +321,7 @@ public void accept(ByteBuf buf) {
receiver.unprotect(protect, unprotectOut, alloc);
fail("Exception expected");
} catch (AEADBadTagException ex) {
assertThat(ex).hasMessageThat().contains(DECRYPTION_FAILURE_RE);
assertThat(ex).hasMessageThat().containsMatch(DECRYPTION_FAILURE_RE);
}

sender.destroy();
Expand Down Expand Up @@ -360,7 +360,7 @@ public void accept(ByteBuf buf) {
receiver.unprotect(protect, unprotectOut, alloc);
fail("Exception expected");
} catch (AEADBadTagException ex) {
assertThat(ex).hasMessageThat().contains(DECRYPTION_FAILURE_RE);
assertThat(ex).hasMessageThat().containsMatch(DECRYPTION_FAILURE_RE);
}

sender.destroy();
Expand Down Expand Up @@ -396,7 +396,7 @@ public void accept(ByteBuf buf) {
sender.unprotect(protect.slice(), unprotectOut, alloc);
fail("Exception expected");
} catch (AEADBadTagException ex) {
assertThat(ex).hasMessageThat().contains(DECRYPTION_FAILURE_RE);
assertThat(ex).hasMessageThat().containsMatch(DECRYPTION_FAILURE_RE);
}

sender.destroy();
Expand Down
2 changes: 1 addition & 1 deletion build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -152,7 +152,7 @@ subprojects {
// examples/example-tls/pom.xml
netty_tcnative: 'io.netty:netty-tcnative-boringssl-static:2.0.25.Final',

conscrypt: 'org.conscrypt:conscrypt-openjdk-uber:1.0.1',
conscrypt: 'org.conscrypt:conscrypt-openjdk-uber:2.2.1',
re2j: 'com.google.re2j:re2j:1.2',

// Test dependencies.
Expand Down
14 changes: 14 additions & 0 deletions testing/src/main/java/io/grpc/internal/testing/TestUtils.java
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@

package io.grpc.internal.testing;

import com.google.common.base.Throwables;
import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.File;
Expand Down Expand Up @@ -156,6 +157,12 @@ public static void installConscryptIfAvailable() {
if (conscryptInstallAttempted) {
return;
}
// Conscrypt-based I/O (like used in OkHttp) breaks on Windows.
// https://github.com/google/conscrypt/issues/444
if (System.mapLibraryName("test").endsWith(".dll")) {
conscryptInstallAttempted = true;
return;
}
Class<?> conscrypt;
try {
conscrypt = Class.forName("org.conscrypt.Conscrypt");
Expand All @@ -175,6 +182,13 @@ public static void installConscryptIfAvailable() {
} catch (IllegalAccessException ex) {
throw new RuntimeException("Could not invoke Conscrypt.newProvider", ex);
} catch (InvocationTargetException ex) {
Throwable root = Throwables.getRootCause(ex);
// Conscrypt uses a newer version of glibc than available on RHEL 6
if (root instanceof UnsatisfiedLinkError && root.getMessage() != null
&& root.getMessage().contains("GLIBC_2.14")) {
conscryptInstallAttempted = true;
return;
}
throw new RuntimeException("Could not invoke Conscrypt.newProvider", ex);
}
Security.addProvider(provider);
Expand Down

0 comments on commit 0be86a5

Please sign in to comment.