Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

support tls single authentication #592

Merged
merged 2 commits into from
Mar 27, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,8 @@ public class NebulaPoolConfig implements Serializable {
// Set to true to turn on ssl encrypted traffic
private boolean enableSsl = false;

// SSL param is required if ssl is turned on
// if enableSsl is true but SSL param is not config,
// then client will not verify the server certificate. Encrypted transmission only.
private SSLParam sslParam = null;

// Set if use http2 protocol
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,10 @@ public class CASignedSSLParam extends SSLParam {
private String crtFilePath;
private String keyFilePath;

public CASignedSSLParam() {
super(SignMode.CA_SIGNED);
}

public CASignedSSLParam(String caCrtFilePath, String crtFilePath, String keyFilePath) {
super(SignMode.CA_SIGNED);
this.caCrtFilePath = caCrtFilePath;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@ public abstract class SSLParam implements Serializable {

private static final long serialVersionUID = 7410233298826490747L;

private boolean skipVerifyServer = false;

public enum SignMode {
NONE,
SELF_SIGNED,
Expand All @@ -19,6 +21,14 @@ public enum SignMode {

private SignMode signMode;

public boolean isSkipVerifyServer() {
return skipVerifyServer;
}

public void setSkipVerifyServer(boolean skipVerifyServer) {
this.skipVerifyServer = skipVerifyServer;
}

public SSLParam(SignMode signMode) {
this.signMode = signMode;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -35,10 +35,6 @@ public SyncConnection create() throws IOErrorException, ClientServerIncompatible
while (retry-- > 0) {
try {
if (config.isEnableSsl()) {
if (config.getSslParam() == null) {
throw new IllegalArgumentException("SSL Param is required when enableSsl "
+ "is set to true");
}
conn.open(address, config.getTimeout(), config.getSslParam(),
config.isUseHttp2(), config.getCustomHeaders());
} else {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -109,6 +109,7 @@ public boolean ping(HostAddress addr) {
connection.close();
return pong;
} catch (IOErrorException e) {
LOGGER.warn(String.format("ping server %s failed", addr.toString()), e);
return false;
} catch (ClientServerIncompatibleException e) {
LOGGER.error("version verify failed, ", e);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -73,7 +73,9 @@ public void open(HostAddress address, int timeout, SSLParam sslParam, boolean is
this.useHttp2 = isUseHttp2;
this.headers = headers;
if (sslSocketFactory == null) {
if (sslParam.getSignMode() == SSLParam.SignMode.CA_SIGNED) {
if (sslParam == null) {
sslSocketFactory = SslUtil.getSSLSocketFactoryWithoutVerify();
} else if (sslParam.getSignMode() == SSLParam.SignMode.CA_SIGNED) {
sslSocketFactory =
SslUtil.getSSLSocketFactoryWithCA((CASignedSSLParam) sslParam);
} else {
Expand Down
88 changes: 88 additions & 0 deletions client/src/main/java/com/vesoft/nebula/util/SslUtil.java
Original file line number Diff line number Diff line change
Expand Up @@ -7,17 +7,30 @@

import com.vesoft.nebula.client.graph.data.CASignedSSLParam;
import com.vesoft.nebula.client.graph.data.SelfSignedSSLParam;
import java.io.FileInputStream;
import java.io.FileReader;
import java.io.InputStream;
import java.security.KeyPair;
import java.security.KeyStore;
import java.security.Security;
import java.security.cert.CertPath;
import java.security.cert.CertPathValidator;
import java.security.cert.Certificate;
import java.security.cert.CertificateException;
import java.security.cert.CertificateFactory;
import java.security.cert.PKIXParameters;
import java.security.cert.TrustAnchor;
import java.security.cert.X509Certificate;
import java.util.Arrays;
import java.util.HashSet;
import java.util.Set;
import javax.net.ssl.KeyManager;
import javax.net.ssl.KeyManagerFactory;
import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLSocketFactory;
import javax.net.ssl.TrustManager;
import javax.net.ssl.TrustManagerFactory;
import javax.net.ssl.X509TrustManager;
import org.bouncycastle.cert.X509CertificateHolder;
import org.bouncycastle.cert.jcajce.JcaX509CertificateConverter;
import org.bouncycastle.jce.provider.BouncyCastleProvider;
Expand All @@ -36,6 +49,20 @@ public class SslUtil {
private static TrustManager[] trustManagers;

public static SSLSocketFactory getSSLSocketFactoryWithCA(CASignedSSLParam param) {
// skip the verify for server certificate
if (param.isSkipVerifyServer()) {
return getSSLSocketFactoryWithoutVerify();
}

// if cert and key are null, consider the server certificate is CA,
// and verify if server certificate is CA.
if (param.getCaCrtFilePath() == null) {
return getSSLSocketFactoryVerifyCACert();
}


// verify server certificate using client config, the server certificate must be issued
// by client caCrt.
final String caCrtFile = param.getCaCrtFilePath();
final String crtFile = param.getCrtFilePath();
final String keyFile = param.getKeyFilePath();
Expand Down Expand Up @@ -220,4 +247,65 @@ public static SSLSocketFactory getSSLSocketFactoryWithoutCA(SelfSignedSSLParam p
public static TrustManager[] getTrustManagers() {
return trustManagers;
}


public static SSLSocketFactory getSSLSocketFactoryWithoutVerify() {
TrustManager[] trustManagers = new TrustManager[]{
new X509TrustManager() {
public java.security.cert.X509Certificate[] getAcceptedIssuers() {
return null;
}

public void checkClientTrusted(
java.security.cert.X509Certificate[] certs, String authType) {
}

public void checkServerTrusted(
java.security.cert.X509Certificate[] certs, String authType) {
}
}
};

try {
SSLContext sslContext = SSLContext.getInstance("TLS");
sslContext.init(null, trustManagers, null);
return sslContext.getSocketFactory();
} catch (Exception e) {
LOGGER.error(e.getMessage());
}
return null;
}

/**
* check if the server's cert is issued by a CA
*/
public static SSLSocketFactory getSSLSocketFactoryVerifyCACert() {
try {
String trustStoreUrl = System.getProperty("javax.net.ssl.trustStore");
String trustStorePassword = System.getProperty("javax.net.ssl.trustStorePassword");
KeyStore keystore = KeyStore.getInstance(KeyStore.getDefaultType());

if (trustStoreUrl == null || trustStorePassword == null) {
throw new RuntimeException(
"No truststore provided to verify the Server certificate,"
+ " please set javax.net.ssl.trustStore and "
+ "javax.net.ssl.trustStorePassword for the System.");
}
InputStream in = new FileInputStream(trustStoreUrl);
keystore.load(in, trustStorePassword.toCharArray());

KeyManagerFactory kmf = KeyManagerFactory.getInstance("SunX509");
kmf.init(keystore, trustStorePassword.toCharArray());

TrustManagerFactory tmf = TrustManagerFactory.getInstance("SunX509");
tmf.init(keystore);

SSLContext sslContext = SSLContext.getInstance("TLS");
sslContext.init(kmf.getKeyManagers(), tmf.getTrustManagers(), null);
return sslContext.getSocketFactory();
} catch (Exception e) {
LOGGER.error("get SSLSocketFactory error,", e);
}
return null;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
/* Copyright (c) 2024 vesoft inc. All rights reserved.
*
* This source code is licensed under Apache 2.0 License.
*/

package com.vesoft.nebula.examples;

import com.vesoft.nebula.client.graph.NebulaPoolConfig;
import com.vesoft.nebula.client.graph.data.CASignedSSLParam;
import com.vesoft.nebula.client.graph.data.HostAddress;
import com.vesoft.nebula.client.graph.data.ResultSet;
import com.vesoft.nebula.client.graph.exception.AuthFailedException;
import com.vesoft.nebula.client.graph.exception.ClientServerIncompatibleException;
import com.vesoft.nebula.client.graph.exception.IOErrorException;
import com.vesoft.nebula.client.graph.exception.NotValidConnectionException;
import com.vesoft.nebula.client.graph.net.NebulaPool;
import com.vesoft.nebula.client.graph.net.Session;
import java.net.UnknownHostException;
import java.util.Arrays;

/**
* if you trust server's self sign certificate, import the root certificate who issues
* server certificate into client system trust store using keytool:
* keytool -import -trustcacerts -alias root_ca -file Desktop/root.crt -keystore truststore.jks
* And load the local truststore.jks into system:
* System.setProperty("javax.net.ssl.trustStore", "/Users/nicole/truststore.jks");
* System.setProperty("javax.net.ssl.trustStorePassword", "123456");
*/

public class GraphSSLExample {
public static void main(String[] args) throws UnknownHostException, IOErrorException,
AuthFailedException, ClientServerIncompatibleException, NotValidConnectionException {
// if server use the certificate issued by CA , then please make sure your client trust
// store has been load in System.
String trustStorePath = System.getenv("JAVA_HOME") + "/jre/lib/security/cacerts";
System.setProperty("javax.net.ssl.trustStore", trustStorePath);
// default password of JDK cacerts is changeit
System.setProperty("javax.net.ssl.trustStorePassword", "changeit");


NebulaPoolConfig nebulaSslPoolConfig = new NebulaPoolConfig();
nebulaSslPoolConfig.setMaxConnSize(100);
// enable ssl
nebulaSslPoolConfig.setEnableSsl(true);
// config ssl is CA and config no certificate
nebulaSslPoolConfig.setSslParam(new CASignedSSLParam());

NebulaPool sslPool = new NebulaPool();
sslPool.init(Arrays.asList(new HostAddress(
"nebula-graph-nco13931ssfnnd8o6bk50.aws.dev.cloud.nebula-graph.io", 9669)),
nebulaSslPoolConfig);
String query = "YIELD 1";
Session sslSession = sslPool.getSession("[email protected]",
"H3prCPAh1POJEgxPkReEQQ", false);
ResultSet resp = sslSession.execute(query);

if (!resp.isSucceeded()) {
System.out.println(
String.format("Execute: `%s', failed: %s", query, resp.getErrorMessage()));
System.exit(1);
}
System.out.println(resp);
sslSession.release();
sslPool.close();
}
}
Loading