1 /* 2 * Copyright 2014 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.internal.testing; 18 19 import java.io.BufferedInputStream; 20 import java.io.BufferedOutputStream; 21 import java.io.File; 22 import java.io.FileInputStream; 23 import java.io.FileOutputStream; 24 import java.io.IOException; 25 import java.io.InputStream; 26 import java.io.OutputStream; 27 import java.lang.reflect.InvocationTargetException; 28 import java.lang.reflect.Method; 29 import java.net.InetAddress; 30 import java.net.InetSocketAddress; 31 import java.net.UnknownHostException; 32 import java.security.KeyStore; 33 import java.security.NoSuchAlgorithmException; 34 import java.security.Provider; 35 import java.security.Security; 36 import java.security.cert.CertificateException; 37 import java.security.cert.CertificateFactory; 38 import java.security.cert.X509Certificate; 39 import java.util.ArrayList; 40 import java.util.Collections; 41 import java.util.List; 42 import java.util.concurrent.TimeUnit; 43 import javax.net.ssl.SSLContext; 44 import javax.net.ssl.SSLSocketFactory; 45 import javax.net.ssl.TrustManagerFactory; 46 import javax.security.auth.x500.X500Principal; 47 48 /** 49 * Internal utility functions useful for writing tests. 50 */ 51 public class TestUtils { 52 public static final String TEST_SERVER_HOST = "foo.test.google.fr"; 53 54 /** 55 * Creates a new {@link InetSocketAddress} that overrides the host with {@link #TEST_SERVER_HOST}. 56 */ testServerAddress(String host, int port)57 public static InetSocketAddress testServerAddress(String host, int port) { 58 try { 59 InetAddress inetAddress = InetAddress.getByName(host); 60 inetAddress = InetAddress.getByAddress(TEST_SERVER_HOST, inetAddress.getAddress()); 61 return new InetSocketAddress(inetAddress, port); 62 } catch (UnknownHostException e) { 63 throw new RuntimeException(e); 64 } 65 } 66 67 /** 68 * Creates a new {@link InetSocketAddress} on localhost that overrides the host with 69 * {@link #TEST_SERVER_HOST}. 70 */ testServerAddress(int port)71 public static InetSocketAddress testServerAddress(int port) { 72 try { 73 InetAddress inetAddress = InetAddress.getByName("localhost"); 74 inetAddress = InetAddress.getByAddress(TEST_SERVER_HOST, inetAddress.getAddress()); 75 return new InetSocketAddress(inetAddress, port); 76 } catch (UnknownHostException e) { 77 throw new RuntimeException(e); 78 } 79 } 80 81 /** 82 * Returns the ciphers preferred to use during tests. They may be chosen because they are widely 83 * available or because they are fast. There is no requirement that they provide confidentiality 84 * or integrity. 85 */ preferredTestCiphers()86 public static List<String> preferredTestCiphers() { 87 String[] ciphers; 88 try { 89 ciphers = SSLContext.getDefault().getDefaultSSLParameters().getCipherSuites(); 90 } catch (NoSuchAlgorithmException ex) { 91 throw new RuntimeException(ex); 92 } 93 List<String> ciphersMinusGcm = new ArrayList<>(); 94 for (String cipher : ciphers) { 95 // The GCM implementation in Java is _very_ slow (~1 MB/s) 96 if (cipher.contains("_GCM_")) { 97 continue; 98 } 99 ciphersMinusGcm.add(cipher); 100 } 101 return Collections.unmodifiableList(ciphersMinusGcm); 102 } 103 104 /** 105 * Saves a file from the classpath resources in src/main/resources/certs as a file on the 106 * filesystem. 107 * 108 * @param name name of a file in src/main/resources/certs. 109 */ loadCert(String name)110 public static File loadCert(String name) throws IOException { 111 InputStream 112 in = new BufferedInputStream(TestUtils.class.getResourceAsStream("/certs/" + name)); 113 File tmpFile = File.createTempFile(name, ""); 114 tmpFile.deleteOnExit(); 115 116 OutputStream os = new BufferedOutputStream(new FileOutputStream(tmpFile)); 117 try { 118 int b; 119 while ((b = in.read()) != -1) { 120 os.write(b); 121 } 122 os.flush(); 123 } finally { 124 in.close(); 125 os.close(); 126 } 127 128 return tmpFile; 129 } 130 131 /** 132 * Loads an X.509 certificate from the classpath resources in src/main/resources/certs. 133 * 134 * @param fileName name of a file in src/main/resources/certs. 135 */ loadX509Cert(String fileName)136 public static X509Certificate loadX509Cert(String fileName) 137 throws CertificateException, IOException { 138 CertificateFactory cf = CertificateFactory.getInstance("X.509"); 139 140 InputStream in = TestUtils.class.getResourceAsStream("/certs/" + fileName); 141 try { 142 return (X509Certificate) cf.generateCertificate(in); 143 } finally { 144 in.close(); 145 } 146 } 147 148 private static boolean conscryptInstallAttempted; 149 150 /** 151 * Add Conscrypt to the list of security providers, if it is available. If it appears to be 152 * available but fails to load, this method will throw an exception. Since the list of security 153 * providers is static, this method does nothing if the provider is not available or succeeded 154 * previously. 155 */ installConscryptIfAvailable()156 public static void installConscryptIfAvailable() { 157 if (conscryptInstallAttempted) { 158 return; 159 } 160 Class<?> conscrypt; 161 try { 162 conscrypt = Class.forName("org.conscrypt.Conscrypt"); 163 } catch (ClassNotFoundException ex) { 164 conscryptInstallAttempted = true; 165 return; 166 } 167 Method newProvider; 168 try { 169 newProvider = conscrypt.getMethod("newProvider"); 170 } catch (NoSuchMethodException ex) { 171 throw new RuntimeException("Could not find newProvider method on Conscrypt", ex); 172 } 173 Provider provider; 174 try { 175 provider = (Provider) newProvider.invoke(null); 176 } catch (IllegalAccessException ex) { 177 throw new RuntimeException("Could not invoke Conscrypt.newProvider", ex); 178 } catch (InvocationTargetException ex) { 179 throw new RuntimeException("Could not invoke Conscrypt.newProvider", ex); 180 } 181 Security.addProvider(provider); 182 conscryptInstallAttempted = true; 183 } 184 185 /** 186 * Creates an SSLSocketFactory which contains {@code certChainFile} as its only root certificate. 187 */ newSslSocketFactoryForCa(Provider provider, File certChainFile)188 public static SSLSocketFactory newSslSocketFactoryForCa(Provider provider, 189 File certChainFile) throws Exception { 190 KeyStore ks = KeyStore.getInstance(KeyStore.getDefaultType()); 191 ks.load(null, null); 192 CertificateFactory cf = CertificateFactory.getInstance("X.509"); 193 X509Certificate cert = (X509Certificate) cf.generateCertificate( 194 new BufferedInputStream(new FileInputStream(certChainFile))); 195 X500Principal principal = cert.getSubjectX500Principal(); 196 ks.setCertificateEntry(principal.getName("RFC2253"), cert); 197 198 // Set up trust manager factory to use our key store. 199 TrustManagerFactory trustManagerFactory = 200 TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm()); 201 trustManagerFactory.init(ks); 202 SSLContext context = SSLContext.getInstance("TLS", provider); 203 context.init(null, trustManagerFactory.getTrustManagers(), null); 204 return context.getSocketFactory(); 205 } 206 207 /** 208 * Sleeps for at least the specified time. When in need of a guaranteed sleep time, use this in 209 * preference to {@code Thread.sleep} which might not sleep for the required time. 210 */ sleepAtLeast(long millis)211 public static void sleepAtLeast(long millis) throws InterruptedException { 212 long delay = TimeUnit.MILLISECONDS.toNanos(millis); 213 long end = System.nanoTime() + delay; 214 while (delay > 0) { 215 TimeUnit.NANOSECONDS.sleep(delay); 216 delay = end - System.nanoTime(); 217 } 218 } 219 TestUtils()220 private TestUtils() {} 221 } 222