From e9921b77f2489c012c80daff1ba2738571aea408 Mon Sep 17 00:00:00 2001 From: Eric Anderson Date: Thu, 3 Oct 2019 10:36:37 -0700 Subject: [PATCH] Create ConscryptLoader for code sharing --- .../grpc/alts/internal/AesGcmAeadCrypter.java | 44 ++-------- .../io/grpc/internal/ConscryptLoader.java | 80 +++++++++++++++++++ .../io/grpc/internal/testing/TestUtils.java | 24 ++---- 3 files changed, 93 insertions(+), 55 deletions(-) create mode 100644 core/src/main/java/io/grpc/internal/ConscryptLoader.java diff --git a/alts/src/main/java/io/grpc/alts/internal/AesGcmAeadCrypter.java b/alts/src/main/java/io/grpc/alts/internal/AesGcmAeadCrypter.java index 3e0bf2f84b..bd072eab42 100644 --- a/alts/src/main/java/io/grpc/alts/internal/AesGcmAeadCrypter.java +++ b/alts/src/main/java/io/grpc/alts/internal/AesGcmAeadCrypter.java @@ -18,7 +18,7 @@ package io.grpc.alts.internal; import static com.google.common.base.Preconditions.checkArgument; -import java.lang.reflect.Method; +import io.grpc.internal.ConscryptLoader; import java.nio.ByteBuffer; import java.security.GeneralSecurityException; import java.security.Provider; @@ -111,8 +111,9 @@ final class AesGcmAeadCrypter implements AeadCrypter { } private static Provider getConscrypt() { - // This is equivalent to if (Conscrypt.isAvailable()) return Conscrypt.newProvider(); - + if (!ConscryptLoader.isPresent()) { + return null; + } // 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" @@ -120,42 +121,11 @@ final class AesGcmAeadCrypter implements AeadCrypter { // // 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 ConscryptLoader.newProvider(); + } catch (Throwable t) { + logger.log(Level.INFO, "Could not load Conscrypt. Will use slower JDK implementation", t); 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; } } diff --git a/core/src/main/java/io/grpc/internal/ConscryptLoader.java b/core/src/main/java/io/grpc/internal/ConscryptLoader.java new file mode 100644 index 0000000000..2a0d9e47ce --- /dev/null +++ b/core/src/main/java/io/grpc/internal/ConscryptLoader.java @@ -0,0 +1,80 @@ +/* + * Copyright 2019 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.internal; + +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.security.Provider; + +/** + * Utility to load dynamically Conscrypt when it is available. + */ +public final class ConscryptLoader { + private static final Method NEW_PROVIDER_METHOD; + private static final Method IS_CONSCRYPT_METHOD; + + static { + Method newProvider; + Method isConscrypt; + try { + Class conscryptClass = Class.forName("org.conscrypt.Conscrypt"); + newProvider = conscryptClass.getMethod("newProvider"); + isConscrypt = conscryptClass.getMethod("isConscrypt", Provider.class); + } catch (ClassNotFoundException ex) { + newProvider = null; + isConscrypt = null; + } catch (NoSuchMethodException ex) { + throw new AssertionError(ex); + } + NEW_PROVIDER_METHOD = newProvider; + IS_CONSCRYPT_METHOD = isConscrypt; + } + + /** + * Returns {@code true} when the Conscrypt Java classes are available. Does not imply it actually + * works on this platform. + */ + public static boolean isPresent() { + return NEW_PROVIDER_METHOD != null; + } + + /** Same as {@code Conscrypt.isConscrypt(Provider)}. */ + public static boolean isConscrypt(Provider provider) { + if (!isPresent()) { + return false; + } + try { + return (Boolean) IS_CONSCRYPT_METHOD.invoke(null, provider); + } catch (IllegalAccessException ex) { + throw new AssertionError(ex); + } catch (InvocationTargetException ex) { + throw new AssertionError(ex); + } + } + + /** Same as {@code Conscrypt.newProvider()}. */ + public static Provider newProvider() throws Throwable { + if (!isPresent()) { + Class.forName("org.conscrypt.Conscrypt"); + throw new AssertionError("Unexpected failure referencing Conscrypt class"); + } + // Exceptions here probably mean something's wrong with the JNI loading. Maybe the platform is + // not supported. It's an error, but it may occur in some environments as part of normal + // operation. It's too hard to distinguish "normal" from "abnormal" failures here. + return (Provider) NEW_PROVIDER_METHOD.invoke(null); + } +} diff --git a/testing/src/main/java/io/grpc/internal/testing/TestUtils.java b/testing/src/main/java/io/grpc/internal/testing/TestUtils.java index f80f6a763a..6b467eff52 100644 --- a/testing/src/main/java/io/grpc/internal/testing/TestUtils.java +++ b/testing/src/main/java/io/grpc/internal/testing/TestUtils.java @@ -17,6 +17,7 @@ package io.grpc.internal.testing; import com.google.common.base.Throwables; +import io.grpc.internal.ConscryptLoader; import java.io.BufferedInputStream; import java.io.BufferedOutputStream; import java.io.File; @@ -25,8 +26,6 @@ import java.io.FileOutputStream; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; -import java.lang.reflect.InvocationTargetException; -import java.lang.reflect.Method; import java.net.InetAddress; import java.net.InetSocketAddress; import java.net.UnknownHostException; @@ -163,33 +162,22 @@ public class TestUtils { conscryptInstallAttempted = true; return; } - Class conscrypt; - try { - conscrypt = Class.forName("org.conscrypt.Conscrypt"); - } catch (ClassNotFoundException ex) { + if (!ConscryptLoader.isPresent()) { conscryptInstallAttempted = true; return; } - Method newProvider; - try { - newProvider = conscrypt.getMethod("newProvider"); - } catch (NoSuchMethodException ex) { - throw new RuntimeException("Could not find newProvider method on Conscrypt", ex); - } Provider provider; try { - provider = (Provider) newProvider.invoke(null); - } catch (IllegalAccessException ex) { - throw new RuntimeException("Could not invoke Conscrypt.newProvider", ex); - } catch (InvocationTargetException ex) { - Throwable root = Throwables.getRootCause(ex); + provider = ConscryptLoader.newProvider(); + } catch (Throwable t) { + Throwable root = Throwables.getRootCause(t); // 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); + throw new RuntimeException("Could not create Conscrypt provider", t); } Security.addProvider(provider); conscryptInstallAttempted = true;