forked from hyperledger-web3j/web3j
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
1. Added support for specifying Ethereum client versions when working…
… with Infura. 2. Updated network diagram.
- Loading branch information
Showing
5 changed files
with
305 additions
and
9 deletions.
There are no files selected for viewing
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
150 changes: 150 additions & 0 deletions
150
src/main/java/org/web3j/protocol/infura/CertificateManager.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,150 @@ | ||
package org.web3j.protocol.infura; | ||
|
||
import javax.net.ssl.*; | ||
import java.io.File; | ||
import java.io.FileOutputStream; | ||
import java.io.IOException; | ||
import java.net.URI; | ||
import java.net.URISyntaxException; | ||
import java.security.*; | ||
import java.security.cert.CertificateException; | ||
import java.security.cert.X509Certificate; | ||
|
||
/** | ||
* Certificate manager to simplify working with TLS endpoints. | ||
*/ | ||
public class CertificateManager { | ||
|
||
static File buildKeyStore(String url, char[] keyStorePassword) { | ||
KeyStore keyStore; | ||
try { | ||
keyStore = KeyStore.getInstance(KeyStore.getDefaultType()); | ||
keyStore.load(null, keyStorePassword); | ||
|
||
CertificateChainTrustManager certificateChainTrustManager = | ||
createCertificateChainTrustManager(keyStore); | ||
URI endpoint = new URI(url); | ||
SSLSocket sslSocket = createSslSocket(endpoint, certificateChainTrustManager); | ||
|
||
if (!isTrustedEndPoint(sslSocket)) { | ||
X509Certificate[] x509Certificates = certificateChainTrustManager.x509Certificates; | ||
if (x509Certificates == null) { | ||
throw new RuntimeException("Unable to obtain x509 certificate from server"); | ||
} | ||
|
||
for (int i = 0; i < x509Certificates.length; i++) { | ||
keyStore.setCertificateEntry(endpoint.getHost() + i, x509Certificates[i]); | ||
} | ||
} | ||
|
||
SecureRandom random = new SecureRandom(); | ||
File keyFile = File.createTempFile("web3j-", "" + random.nextLong()); | ||
|
||
FileOutputStream fileOutputStream = new FileOutputStream(keyFile); | ||
keyStore.store(fileOutputStream, keyStorePassword); | ||
fileOutputStream.close(); | ||
|
||
deleteFileOnShutdown(keyFile); | ||
|
||
return keyFile; | ||
|
||
} catch (KeyStoreException e) { | ||
throw new RuntimeException(e); | ||
} catch (CertificateException e) { | ||
throw new RuntimeException(e); | ||
} catch (NoSuchAlgorithmException e) { | ||
throw new RuntimeException(e); | ||
} catch (IOException e) { | ||
throw new RuntimeException(e); | ||
} catch (KeyManagementException e) { | ||
throw new RuntimeException(e); | ||
} catch (URISyntaxException e) { | ||
throw new RuntimeException(e); | ||
} | ||
} | ||
|
||
private static CertificateChainTrustManager createCertificateChainTrustManager( | ||
KeyStore keyStore) throws NoSuchAlgorithmException, KeyStoreException { | ||
|
||
TrustManagerFactory trustManagerFactory = | ||
TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm()); | ||
trustManagerFactory.init(keyStore); | ||
X509TrustManager defaultTrustManager = | ||
(X509TrustManager) trustManagerFactory.getTrustManagers()[0]; | ||
return new CertificateChainTrustManager(defaultTrustManager); | ||
} | ||
|
||
private static SSLSocket createSslSocket( | ||
URI endpoint, CertificateChainTrustManager certificateChainTrustManager) | ||
throws NoSuchAlgorithmException, KeyManagementException, IOException, | ||
URISyntaxException, KeyStoreException { | ||
|
||
SSLContext context = SSLContext.getInstance("TLS"); | ||
context.init(null, new TrustManager[]{certificateChainTrustManager}, null); | ||
SSLSocketFactory factory = context.getSocketFactory(); | ||
|
||
SSLSocket sslSocket = (SSLSocket) factory.createSocket( | ||
endpoint.getHost(), 443); | ||
sslSocket.setSoTimeout(10000); | ||
|
||
return sslSocket; | ||
} | ||
|
||
private static boolean isTrustedEndPoint(SSLSocket socket) throws IOException { | ||
try { | ||
socket.startHandshake(); | ||
socket.close(); | ||
return true; | ||
} catch (SSLException e) { | ||
return false; | ||
} | ||
} | ||
|
||
private static void deleteFileOnShutdown(File file) { | ||
Runtime.getRuntime().addShutdownHook(new Thread() { | ||
public void run() { | ||
try { | ||
deleteTempFile(file); | ||
} catch (IOException e) { | ||
throw new RuntimeException(e); | ||
} | ||
} | ||
}); | ||
} | ||
|
||
private static void deleteTempFile(File file) throws IOException { | ||
if (file.exists() && !file.delete()) { | ||
throw new RuntimeException("Unable to remove file: " + file.getCanonicalPath()); | ||
} | ||
} | ||
|
||
/** | ||
* Based on: | ||
* http://blogs.sun.com/andreas/resource/InstallCert.java | ||
*/ | ||
private static class CertificateChainTrustManager implements X509TrustManager { | ||
|
||
private final X509TrustManager x509TrustManager; | ||
private X509Certificate[] x509Certificates; | ||
|
||
CertificateChainTrustManager(X509TrustManager x509TrustManager) { | ||
this.x509TrustManager = x509TrustManager; | ||
} | ||
|
||
public X509Certificate[] getAcceptedIssuers() { | ||
// Called by Java 7+ see http://infposs.blogspot.kr/2013/06/installcert-and-java-7.html | ||
return new X509Certificate[0]; | ||
} | ||
|
||
public void checkClientTrusted(X509Certificate[] chain, String authType) | ||
throws CertificateException { | ||
throw new UnsupportedOperationException(); | ||
} | ||
|
||
public void checkServerTrusted(X509Certificate[] chain, String authType) | ||
throws CertificateException { | ||
this.x509Certificates = chain; | ||
x509TrustManager.checkServerTrusted(chain, authType); | ||
} | ||
} | ||
} |
100 changes: 100 additions & 0 deletions
100
src/main/java/org/web3j/protocol/infura/InfuraHttpService.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,100 @@ | ||
package org.web3j.protocol.infura; | ||
|
||
import javax.net.ssl.*; | ||
import java.io.File; | ||
import java.io.IOException; | ||
import java.security.*; | ||
import java.security.cert.CertificateException; | ||
import java.util.List; | ||
|
||
import org.apache.http.Header; | ||
import org.apache.http.impl.client.CloseableHttpClient; | ||
import org.apache.http.impl.client.HttpClients; | ||
import org.apache.http.message.BasicHeader; | ||
import org.apache.http.ssl.SSLContexts; | ||
|
||
import org.web3j.protocol.http.HttpService; | ||
|
||
/** | ||
* HttpService for working with <a href="https://infura.io/">Infura</a> clients. | ||
*/ | ||
public class InfuraHttpService extends HttpService { | ||
|
||
private static final String INFURA_ETHEREUM_PREFERRED_CLIENT = | ||
"Infura-Ethereum-Preferred-Client"; | ||
|
||
private static final char[] TEMP_KEY_STORE_PASSWORD = "web3j runtime cert store".toCharArray(); | ||
|
||
private final Header clientVersionHeader; | ||
|
||
public InfuraHttpService(String url, String clientVersion, boolean required) { | ||
super(url); | ||
setHttpClient(createTrustTlsHttpClient(url)); | ||
clientVersionHeader = buildHeader(clientVersion, required); | ||
} | ||
|
||
public InfuraHttpService(String url, String clientVersion) { | ||
this(url, clientVersion, true); | ||
} | ||
|
||
@Override | ||
protected void addHeaders(List<Header> headers) { | ||
if (clientVersionHeader != null) { | ||
headers.add(clientVersionHeader); | ||
} | ||
} | ||
|
||
static Header buildHeader(String clientVersion, boolean required) { | ||
if (clientVersion == null || clientVersion.equals("")) { | ||
return null; | ||
} | ||
|
||
if (required) { | ||
return new BasicHeader(INFURA_ETHEREUM_PREFERRED_CLIENT, clientVersion); | ||
} else { | ||
return new BasicHeader( | ||
INFURA_ETHEREUM_PREFERRED_CLIENT, clientVersion + "; required=false"); | ||
} | ||
} | ||
|
||
/** | ||
* Create an {@link InfuraHttpService} instance with a local keystore that implicitly trusts | ||
* the provided endpoint. | ||
* | ||
* This is achieved by creating a local temporary keystore file which we add the certificate | ||
* of the endpoint to upon application startup. | ||
* | ||
* @param url we wish to connect to | ||
* @return the file containing the keystore | ||
* @throws UnrecoverableKeyException if keystore file cannot be loaded | ||
* @throws NoSuchAlgorithmException if keystore file cannot be loaded | ||
* @throws KeyStoreException if keystore file cannot be loaded | ||
* @throws KeyManagementException if keystore file cannot be loaded | ||
*/ | ||
private static CloseableHttpClient createTrustTlsHttpClient(String url) { | ||
|
||
File keyFile = CertificateManager.buildKeyStore(url, TEMP_KEY_STORE_PASSWORD); | ||
|
||
SSLContext sslContext; | ||
try { | ||
sslContext = SSLContexts.custom() | ||
.loadTrustMaterial(keyFile, TEMP_KEY_STORE_PASSWORD) | ||
.build(); | ||
} catch (NoSuchAlgorithmException e) { | ||
throw new RuntimeException(e); | ||
} catch (KeyManagementException e) { | ||
throw new RuntimeException(e); | ||
} catch (KeyStoreException e) { | ||
throw new RuntimeException(e); | ||
} catch (CertificateException e) { | ||
throw new RuntimeException(e); | ||
} catch (IOException e) { | ||
throw new RuntimeException(e); | ||
} | ||
|
||
return HttpClients.custom() | ||
.setConnectionManagerShared(true) | ||
.setSSLContext(sslContext) | ||
.build(); | ||
} | ||
} |
29 changes: 29 additions & 0 deletions
29
src/test/java/org/web3j/protocol/infura/InfuraHttpServiceTest.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,29 @@ | ||
package org.web3j.protocol.infura; | ||
|
||
|
||
import org.apache.http.message.BasicHeader; | ||
import org.junit.Test; | ||
|
||
import static org.hamcrest.core.Is.is; | ||
import static org.junit.Assert.assertNull; | ||
import static org.junit.Assert.assertThat; | ||
import static org.web3j.protocol.infura.InfuraHttpService.buildHeader; | ||
|
||
public class InfuraHttpServiceTest { | ||
|
||
@Test | ||
public void testBuildHeader() { | ||
assertNull(buildHeader("", false)); | ||
assertNull(buildHeader(null, false)); | ||
|
||
assertThat(buildHeader("geth 1.4.19", true).toString(), | ||
is(new BasicHeader( | ||
"Infura-Ethereum-Preferred-Client", | ||
"geth 1.4.19").toString())); | ||
|
||
assertThat(buildHeader("geth 1.4.19", false).toString(), | ||
is(new BasicHeader( | ||
"Infura-Ethereum-Preferred-Client", | ||
"geth 1.4.19; required=false").toString())); | ||
} | ||
} |