diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml
index 2243440a..8d6e173e 100644
--- a/.github/workflows/build.yml
+++ b/.github/workflows/build.yml
@@ -169,7 +169,7 @@ jobs:
echo "static closure symbols are exported"; exit 1
fi
mkdir -p test_wallets
- mvn -B test -Dmaven.test.skip=false -Dtest=TestMoneroUtils,TestNativeLibrary -DargLine=-Djava.library.path=build
+ mvn -B test -Dmaven.test.skip=false -Dtest=TestMoneroUtils,TestNativeLibrary,TestSerialization,TestMoneroConnectionManager#testResponseTimesAfterSettingChange -DargLine=-Djava.library.path=build
- name: Upload monero-java library
uses: actions/upload-artifact@v7
@@ -288,7 +288,7 @@ jobs:
MAVEN_OPTS: -Djava.library.path=build
run: |
mkdir -p test_wallets
- mvn -B test -Dmaven.test.skip=false -Dtest=TestMoneroUtils,TestNativeLibrary -DargLine=-Djava.library.path=build
+ mvn -B test -Dmaven.test.skip=false -Dtest=TestMoneroUtils,TestNativeLibrary,TestSerialization,TestMoneroConnectionManager#testResponseTimesAfterSettingChange -DargLine=-Djava.library.path=build
- name: Upload monero-java library
uses: actions/upload-artifact@v7
@@ -398,7 +398,7 @@ jobs:
echo "unexpected exports above"; exit 1
fi
mkdir -p test_wallets
- mvn -B test -Dmaven.test.skip=false -Dtest=TestMoneroUtils,TestNativeLibrary -DargLine=-Djava.library.path=build
+ mvn -B test -Dmaven.test.skip=false -Dtest=TestMoneroUtils,TestNativeLibrary,TestSerialization,TestMoneroConnectionManager#testResponseTimesAfterSettingChange -DargLine=-Djava.library.path=build
- name: Upload monero-java library
uses: actions/upload-artifact@v7
diff --git a/README.md b/README.md
index 8bb84fe0..0f6b7e08 100644
--- a/README.md
+++ b/README.md
@@ -97,7 +97,7 @@ walletFull.close(true);
#### For Gradle, add to build.gradle:
-`compile 'io.github.woodser:monero-java:0.8.58'`
+`compile 'io.github.woodser:monero-java:0.8.59'`
#### For Maven, add to pom.xml:
@@ -105,7 +105,7 @@ walletFull.close(true);
setDaemonConnection(MoneroRpcConnection connection,
Boolean isTrusted,
SslOptions sslOptions)
-setDaemonConnection(MoneroRpcConnection connection,
Boolean isTrusted,
SslOptions sslOptions) setDaemonConnection(MoneroRpcConnection connection,
Boolean isTrusted,
SslOptions sslOptions) voidsetSubaddressLabel(int accountIdx,
int subaddressIdx,
@@ -1425,6 +1427,9 @@ setDaemonConnection
public void setDaemonConnection(MoneroRpcConnection connection,
Boolean isTrusted,
SslOptions sslOptions)
+Explicit SSL options take precedence over the connection's verification setting.
+ The cached connection records the requested allow-any-cert setting, not custom SSL options.
+ Wallet RPC enforces a CA file or fingerprints; otherwise SSL autodetect can accept unverified certificates.
diff --git a/external/monero-cpp b/external/monero-cpp
index 5ac0d137..574ad3a4 160000
--- a/external/monero-cpp
+++ b/external/monero-cpp
@@ -1 +1 @@
-Subproject commit 5ac0d137b6c6052eb5fdd633849180b17cf8fbe3
+Subproject commit 574ad3a4c82870d9550d003a2483e002739ec55a
diff --git a/pom.xml b/pom.xml
index 9aaa9078..5d4af390 100644
--- a/pom.xml
+++ b/pom.xml
@@ -4,7 +4,7 @@
4.0.0
io.github.woodser
monero-java
- 0.8.58
+ 0.8.59
Monero Java Library
A Java library for using Monero
https://github.com/woodser/monero-java
diff --git a/src/main/cpp/monero_jni_bridge.cpp b/src/main/cpp/monero_jni_bridge.cpp
index ddcd6f71..5057e428 100644
--- a/src/main/cpp/monero_jni_bridge.cpp
+++ b/src/main/cpp/monero_jni_bridge.cpp
@@ -95,7 +95,7 @@ void rethrow_java_exception_as_cpp_exception(JNIEnv* env, jthrowable jexception)
throw runtime_error(msg);
}
-void set_daemon_connection(JNIEnv *env, monero_wallet* wallet, jstring juri, jstring jusername, jstring jpassword, jstring jproxy_uri, jint jis_trusted) {
+void set_daemon_connection(JNIEnv *env, monero_wallet* wallet, jstring juri, jstring jusername, jstring jpassword, jstring jproxy_uri, jint jis_trusted, jboolean jssl_verify) {
// collect and release string params
const char* _uri = juri ? env->GetStringUTFChars(juri, NULL) : nullptr;
@@ -116,7 +116,9 @@ void set_daemon_connection(JNIEnv *env, monero_wallet* wallet, jstring juri, jst
// set daemon connection
try {
- wallet->set_daemon_connection(uri, username, password, proxy_uri, is_trusted);
+ auto connection = std::make_shared(uri, username, password, proxy_uri);
+ connection->m_ssl_verify = jssl_verify;
+ wallet->set_daemon_connection(connection, is_trusted);
} catch (...) {
rethrow_cpp_exception_as_java_exception(env);
}
@@ -507,8 +509,8 @@ JNIEXPORT jlong JNICALL Java_monero_wallet_MoneroWalletFull_openWalletDataJni(JN
}
}
-JNIEXPORT jlong JNICALL Java_monero_wallet_MoneroWalletFull_createWalletJni(JNIEnv *env, jclass clazz, jstring jconfig) {
- MTRACE("Java_monero_wallet_MoneroWalletFull_createWalletJni");
+JNIEXPORT jlong JNICALL Java_monero_wallet_MoneroWalletFull_createWalletWithSslJni(JNIEnv *env, jclass clazz, jstring jconfig) {
+ MTRACE("Java_monero_wallet_MoneroWalletFull_createWalletWithSslJni");
// get config as json string
const char* _config = jconfig ? env->GetStringUTFChars(jconfig, NULL) : nullptr;
@@ -556,11 +558,11 @@ JNIEXPORT jboolean JNICALL Java_monero_wallet_MoneroWalletFull_isViewOnlyJni(JNI
return wallet->is_view_only();
}
-JNIEXPORT void JNICALL Java_monero_wallet_MoneroWalletFull_setDaemonConnectionJni(JNIEnv *env, jobject instance, jstring juri, jstring jusername, jstring jpassword, jstring jproxy_uri, jint jis_trusted) {
- MTRACE("Java_monero_wallet_MoneroWalletFull_setDaemonConnectionJni");
+JNIEXPORT void JNICALL Java_monero_wallet_MoneroWalletFull_setDaemonConnectionWithSslJni(JNIEnv *env, jobject instance, jstring juri, jstring jusername, jstring jpassword, jstring jproxy_uri, jint jis_trusted, jboolean jssl_verify) {
+ MTRACE("Java_monero_wallet_MoneroWalletFull_setDaemonConnectionWithSslJni");
monero_wallet* wallet = get_handle(env, instance, JNI_WALLET_HANDLE);
try {
- set_daemon_connection(env, wallet, juri, jusername, jpassword, jproxy_uri, jis_trusted);
+ set_daemon_connection(env, wallet, juri, jusername, jpassword, jproxy_uri, jis_trusted, jssl_verify);
} catch (...) {
rethrow_cpp_exception_as_java_exception(env);
}
@@ -577,8 +579,8 @@ JNIEXPORT jboolean JNICALL Java_monero_wallet_MoneroWalletFull_isDaemonTrustedJn
}
}
-JNIEXPORT jobjectArray JNICALL Java_monero_wallet_MoneroWalletFull_getDaemonConnectionJni(JNIEnv *env, jobject instance) {
- MTRACE("Java_monero_wallet_MoneroWalletFull_getDaemonConnectionJni()");
+JNIEXPORT jobjectArray JNICALL Java_monero_wallet_MoneroWalletFull_getDaemonConnectionWithSslJni(JNIEnv *env, jobject instance) {
+ MTRACE("Java_monero_wallet_MoneroWalletFull_getDaemonConnectionWithSslJni()");
// get wallet
monero_wallet* wallet = get_handle(env, instance, JNI_WALLET_HANDLE);
@@ -588,12 +590,13 @@ JNIEXPORT jobjectArray JNICALL Java_monero_wallet_MoneroWalletFull_getDaemonConn
std::shared_ptr daemon_connection = wallet->get_daemon_connection();
if (daemon_connection == nullptr) return 0;
- // return string[uri, username, password]
- jobjectArray vals = env->NewObjectArray(3, env->FindClass("java/lang/String"), nullptr);
+ // return string[uri, username, password, proxy_uri, ssl_verify]
+ jobjectArray vals = env->NewObjectArray(5, env->FindClass("java/lang/String"), nullptr);
if (daemon_connection->m_uri != boost::none && !daemon_connection->m_uri.get().empty()) env->SetObjectArrayElement(vals, 0, env->NewStringUTF(daemon_connection->m_uri.get().c_str()));
if (daemon_connection->m_username != boost::none && !daemon_connection->m_username.get().empty()) env->SetObjectArrayElement(vals, 1, env->NewStringUTF(daemon_connection->m_username.get().c_str()));
if (daemon_connection->m_password != boost::none && !daemon_connection->m_password.get().empty()) env->SetObjectArrayElement(vals, 2, env->NewStringUTF(daemon_connection->m_password.get().c_str()));
- if (daemon_connection->m_proxy_uri != boost::none && !daemon_connection->m_proxy_uri.get().empty()) env->SetObjectArrayElement(vals, 2, env->NewStringUTF(daemon_connection->m_proxy_uri.get().c_str()));
+ if (daemon_connection->m_proxy_uri != boost::none && !daemon_connection->m_proxy_uri.get().empty()) env->SetObjectArrayElement(vals, 3, env->NewStringUTF(daemon_connection->m_proxy_uri.get().c_str()));
+ env->SetObjectArrayElement(vals, 4, env->NewStringUTF(daemon_connection->m_ssl_verify ? "true" : "false"));
return vals;
} catch (...) {
rethrow_cpp_exception_as_java_exception(env);
diff --git a/src/main/cpp/monero_jni_bridge.h b/src/main/cpp/monero_jni_bridge.h
index cf2ac4b8..5dab86ca 100644
--- a/src/main/cpp/monero_jni_bridge.h
+++ b/src/main/cpp/monero_jni_bridge.h
@@ -65,7 +65,7 @@ JNIEXPORT jlong JNICALL Java_monero_wallet_MoneroWalletFull_openWalletJni(JNIEnv
JNIEXPORT jlong JNICALL Java_monero_wallet_MoneroWalletFull_openWalletDataJni(JNIEnv *, jclass, jstring, jint, jbyteArray, jbyteArray, jboolean);
-JNIEXPORT jlong JNICALL Java_monero_wallet_MoneroWalletFull_createWalletJni(JNIEnv *, jclass, jstring);
+JNIEXPORT jlong JNICALL Java_monero_wallet_MoneroWalletFull_createWalletWithSslJni(JNIEnv *, jclass, jstring);
JNIEXPORT jobjectArray JNICALL Java_monero_wallet_MoneroWalletFull_getSeedLanguagesJni(JNIEnv *, jclass);
@@ -73,9 +73,9 @@ JNIEXPORT jobjectArray JNICALL Java_monero_wallet_MoneroWalletFull_getSeedLangua
JNIEXPORT jboolean JNICALL Java_monero_wallet_MoneroWalletFull_isViewOnlyJni(JNIEnv *, jobject);
-JNIEXPORT void JNICALL Java_monero_wallet_MoneroWalletFull_setDaemonConnectionJni(JNIEnv *, jobject, jstring, jstring, jstring, jstring, jint);
+JNIEXPORT void JNICALL Java_monero_wallet_MoneroWalletFull_setDaemonConnectionWithSslJni(JNIEnv *, jobject, jstring, jstring, jstring, jstring, jint, jboolean);
-JNIEXPORT jobjectArray JNICALL Java_monero_wallet_MoneroWalletFull_getDaemonConnectionJni(JNIEnv *, jobject);
+JNIEXPORT jobjectArray JNICALL Java_monero_wallet_MoneroWalletFull_getDaemonConnectionWithSslJni(JNIEnv *, jobject);
JNIEXPORT jboolean JNICALL Java_monero_wallet_MoneroWalletFull_isConnectedToDaemonJni(JNIEnv *, jobject);
diff --git a/src/main/java/monero/common/MoneroConnectionManager.java b/src/main/java/monero/common/MoneroConnectionManager.java
index 6680a0b2..582464ad 100644
--- a/src/main/java/monero/common/MoneroConnectionManager.java
+++ b/src/main/java/monero/common/MoneroConnectionManager.java
@@ -78,7 +78,7 @@ public class MoneroConnectionManager {
private boolean autoSwitch = DEFAULT_AUTO_SWITCH;
private long timeoutMs = DEFAULT_TIMEOUT;
private TaskLooper poller;
- private Map> responseTimes = new HashMap>();
+ private Map> responseTimes = new HashMap>(); // keyed by uri since connection hashes can change
/**
* Specify behavior when polling.
@@ -175,7 +175,7 @@ public MoneroConnectionManager removeConnection(String uri) {
MoneroRpcConnection connection = getConnectionByUri(uri);
if (connection == null) throw new MoneroError("No connection exists with URI: " + uri);
connections.remove(connection);
- responseTimes.remove(connection);
+ responseTimes.remove(connection.getUri());
if (connection == currentConnection) {
currentConnection = null;
onConnectionChanged(currentConnection);
@@ -637,13 +637,16 @@ private boolean checkConnections(Collection connections, Co
private MoneroRpcConnection processResponses(Collection responses) {
// add new connections
+ Map responsesByUri = new HashMap();
for (MoneroRpcConnection connection : responses) {
- if (!responseTimes.containsKey(connection)) responseTimes.put(connection, new ArrayList());
+ responsesByUri.put(connection.getUri(), connection);
+ if (!responseTimes.containsKey(connection.getUri())) responseTimes.put(connection.getUri(), new ArrayList());
}
// insert response times or null
- for (Entry> responseTime : responseTimes.entrySet()) {
- responseTime.getValue().add(0, responses.contains(responseTime.getKey()) ? responseTime.getKey().getResponseTime() : null);
+ for (Entry> responseTime : responseTimes.entrySet()) {
+ MoneroRpcConnection response = responsesByUri.get(responseTime.getKey());
+ responseTime.getValue().add(0, response == null ? null : response.getResponseTime());
// remove old response times
if (responseTime.getValue().size() > MIN_BETTER_RESPONSES) responseTime.getValue().remove(responseTime.getValue().size() - 1);
@@ -690,15 +693,15 @@ private MoneroRpcConnection getBestConnectionFromPrioritizedResponses(Collection
if (priorityComparator.compare(bestResponse.getPriority(), bestConnection.getPriority()) != 0) return bestResponse;
// keep best connection if not enough data
- if (!responseTimes.containsKey(bestConnection)) return bestConnection;
+ if (!responseTimes.containsKey(bestConnection.getUri()) || responseTimes.get(bestConnection.getUri()).size() < MIN_BETTER_RESPONSES) return bestConnection;
// check if a connection is consistently better
for (MoneroRpcConnection connection : responses) {
if (connection == bestConnection) continue;
- if (!responseTimes.containsKey(connection) || responseTimes.get(connection).size() < MIN_BETTER_RESPONSES) continue;
+ if (!responseTimes.containsKey(connection.getUri()) || responseTimes.get(connection.getUri()).size() < MIN_BETTER_RESPONSES) continue;
boolean better = true;
for (int i = 0; i < MIN_BETTER_RESPONSES; i++) {
- if (responseTimes.get(connection).get(i) == null || responseTimes.get(bestConnection).get(i) == null || responseTimes.get(connection).get(i) > responseTimes.get(bestConnection).get(i)) {
+ if (responseTimes.get(connection.getUri()).get(i) == null || responseTimes.get(bestConnection.getUri()).get(i) == null || responseTimes.get(connection.getUri()).get(i) > responseTimes.get(bestConnection.getUri()).get(i)) {
better = false;
break;
}
diff --git a/src/main/java/monero/common/MoneroRpcConnection.java b/src/main/java/monero/common/MoneroRpcConnection.java
index 6c612b12..3e59b8ee 100644
--- a/src/main/java/monero/common/MoneroRpcConnection.java
+++ b/src/main/java/monero/common/MoneroRpcConnection.java
@@ -52,6 +52,8 @@
/**
* Maintains a connection and sends requests to a Monero RPC API.
+ * Equality compares URI, credentials, proxy, ZMQ URI, and TLS verification.
+ * Do not mutate these settings while using a connection as a hash key.
*
* TODO: refactor MoneroRpcConnection extends MoneroConnection?
*/
@@ -631,7 +633,7 @@ public int hashCode() {
result = prime * result + ((uri == null) ? 0 : uri.hashCode());
result = prime * result + ((username == null) ? 0 : username.hashCode());
result = prime * result + ((zmqUri == null) ? 0 : zmqUri.hashCode());
- result = prime * result + ((proxyUri == null) ? 0 : proxyUri.hashCode());
+ result = prime * result + Boolean.hashCode(sslVerify);
return result;
}
@@ -641,6 +643,7 @@ public boolean equals(Object obj) {
if (obj == null) return false;
if (getClass() != obj.getClass()) return false;
MoneroRpcConnection other = (MoneroRpcConnection) obj;
+ if (sslVerify != other.sslVerify) return false;
if (password == null) {
if (other.password != null) return false;
} else if (!password.equals(other.password)) return false;
diff --git a/src/main/java/monero/common/MoneroUtils.java b/src/main/java/monero/common/MoneroUtils.java
index 50decffb..33f458da 100644
--- a/src/main/java/monero/common/MoneroUtils.java
+++ b/src/main/java/monero/common/MoneroUtils.java
@@ -42,7 +42,7 @@ public class MoneroUtils {
* @return the version of this monero-java library
*/
public static String getVersion() {
- return "0.8.58";
+ return "0.8.59";
}
/**
diff --git a/src/main/java/monero/wallet/MoneroWalletFull.java b/src/main/java/monero/wallet/MoneroWalletFull.java
index 5a2df885..2b37b8a4 100644
--- a/src/main/java/monero/wallet/MoneroWalletFull.java
+++ b/src/main/java/monero/wallet/MoneroWalletFull.java
@@ -319,7 +319,7 @@ public static MoneroWalletFull createWallet(MoneroWalletConfig config) {
private static MoneroWalletFull createWalletFromSeed(MoneroWalletConfig config) {
if (config.getRestoreHeight() == null) config.setRestoreHeight(0l);
- long jniWalletHandle = createWalletJni(serializeWalletConfig(config));
+ long jniWalletHandle = createWalletWithSslJni(serializeWalletConfig(config));
MoneroWalletFull wallet = new MoneroWalletFull(jniWalletHandle, config.getPassword());
return wallet;
}
@@ -328,7 +328,7 @@ private static MoneroWalletFull createWalletFromKeys(MoneroWalletConfig config)
if (config.getRestoreHeight() == null) config.setRestoreHeight(0l);
if (config.getLanguage() == null) config.setLanguage(DEFAULT_LANGUAGE);
try {
- long jniWalletHandle = createWalletJni(serializeWalletConfig(config));
+ long jniWalletHandle = createWalletWithSslJni(serializeWalletConfig(config));
MoneroWalletFull wallet = new MoneroWalletFull(jniWalletHandle, config.getPassword());
return wallet;
} catch (Exception e) {
@@ -338,7 +338,7 @@ private static MoneroWalletFull createWalletFromKeys(MoneroWalletConfig config)
private static MoneroWalletFull createWalletRandom(MoneroWalletConfig config) {
if (config.getLanguage() == null) config.setLanguage(DEFAULT_LANGUAGE);
- long jniWalletHandle = createWalletJni(serializeWalletConfig(config));
+ long jniWalletHandle = createWalletWithSslJni(serializeWalletConfig(config));
return new MoneroWalletFull(jniWalletHandle, config.getPassword());
}
@@ -535,10 +535,10 @@ public void setDaemonConnection(MoneroRpcConnection daemonConnection, Boolean is
beginCall();
try {
int isTrustedJni = isTrusted == null ? -1 : (isTrusted ? 1 : 0); // negative if unset
- if (daemonConnection == null) setDaemonConnectionJni("", "", "", "", isTrustedJni);
+ if (daemonConnection == null) setDaemonConnectionWithSslJni("", "", "", "", isTrustedJni, true);
else {
try {
- setDaemonConnectionJni(daemonConnection.getUri() == null ? "" : daemonConnection.getUri().toString(), daemonConnection.getUsername(), daemonConnection.getPassword(), daemonConnection.getProxyUri(), isTrustedJni);
+ setDaemonConnectionWithSslJni(daemonConnection.getUri() == null ? "" : daemonConnection.getUri().toString(), daemonConnection.getUsername(), daemonConnection.getPassword(), daemonConnection.getProxyUri(), isTrustedJni, daemonConnection.getSslVerify());
} catch (Exception e) {
throw new MoneroError(e.getMessage());
}
@@ -571,8 +571,8 @@ public MoneroRpcConnection getDaemonConnection() {
beginCall();
try {
try {
- String[] vals = getDaemonConnectionJni();
- return vals == null ? null : new MoneroRpcConnection(vals[0], vals[1], vals[2]);
+ String[] vals = getDaemonConnectionWithSslJni();
+ return vals == null ? null : new MoneroRpcConnection(vals[0], vals[1], vals[2], null, vals[3]).setSslVerify(!"false".equals(vals[4]));
} catch (Exception e) {
throw new MoneroError(e.getMessage());
}
@@ -1859,7 +1859,7 @@ public void close(boolean save) {
private native static long openWalletDataJni(String password, int networkType, byte[] keysData, byte[] cacheData, boolean regtest);
- private native static long createWalletJni(String walletConfigJson);
+ private native static long createWalletWithSslJni(String walletConfigJson);
private native long getHeightJni();
@@ -1875,9 +1875,9 @@ public void close(boolean save) {
private native boolean isViewOnlyJni();
- private native void setDaemonConnectionJni(String uri, String username, String password, String proxyUri, int isTrusted);
+ private native void setDaemonConnectionWithSslJni(String uri, String username, String password, String proxyUri, int isTrusted, boolean sslVerify);
- private native String[] getDaemonConnectionJni(); // returns [uri, username, password]
+ private native String[] getDaemonConnectionWithSslJni(); // returns [uri, username, password, proxyUri, sslVerify]
private native boolean isConnectedToDaemonJni();
diff --git a/src/main/java/monero/wallet/MoneroWalletRpc.java b/src/main/java/monero/wallet/MoneroWalletRpc.java
index 0876528f..ca2d71f0 100644
--- a/src/main/java/monero/wallet/MoneroWalletRpc.java
+++ b/src/main/java/monero/wallet/MoneroWalletRpc.java
@@ -510,14 +510,23 @@ public void setDaemonConnection(MoneroRpcConnection connection, Boolean isTruste
setDaemonConnection(connection, isTrusted, null);
}
+ /**
+ * Explicit SSL options take precedence over the connection's verification setting.
+ * The cached connection records the requested allow-any-cert setting, not custom SSL options.
+ * Wallet RPC enforces a CA file or fingerprints; otherwise SSL autodetect can accept unverified certificates.
+ */
public void setDaemonConnection(MoneroRpcConnection connection, Boolean isTrusted, SslOptions sslOptions) {
- if (sslOptions == null) sslOptions = new SslOptions();
+ if (sslOptions == null) {
+ sslOptions = new SslOptions();
+ if (connection != null) sslOptions.setAllowAnyCert(!connection.getSslVerify());
+ }
Map params = new HashMap();
params.put("address", connection == null ? "placeholder" : connection.getUri());
params.put("username", connection == null ? "" : connection.getUsername());
params.put("password", connection == null ? "" : connection.getPassword());
params.put("trusted", isTrusted);
- params.put("ssl_support", "autodetect");
+ boolean hasCertificates = (sslOptions.getCertificateAuthorityFile() != null && !sslOptions.getCertificateAuthorityFile().isEmpty()) || (sslOptions.getAllowedFingerprints() != null && !sslOptions.getAllowedFingerprints().isEmpty());
+ params.put("ssl_support", hasCertificates && !Boolean.TRUE.equals(sslOptions.getAllowAnyCert()) ? "enabled" : "autodetect"); // wallet rpc only enforces certificates if enabled
params.put("ssl_private_key_path", sslOptions.getPrivateKeyPath());
params.put("ssl_certificate_path", sslOptions.getCertificatePath());
params.put("ssl_ca_file", sslOptions.getCertificateAuthorityFile());
@@ -535,8 +544,10 @@ else if (!NetworkUtils.isSameProxyUri(startupProxyUri, connection.getProxyUri())
}
if (!params.containsKey("proxy")) params.put("proxy", "");
+ MoneroRpcConnection daemonConnection = connection == null || connection.getUri() == null || connection.getUri().isEmpty() ? null : new MoneroRpcConnection(connection);
+ if (daemonConnection != null) daemonConnection.setSslVerify(!Boolean.TRUE.equals(params.get("ssl_allow_any_cert")));
rpc.sendJsonRequest("set_daemon", params);
- this.daemonConnection = connection == null || connection.getUri() == null || connection.getUri().isEmpty() ? null : new MoneroRpcConnection(connection);
+ this.daemonConnection = daemonConnection;
}
@Override
diff --git a/src/test/java/TestMoneroConnectionManager.java b/src/test/java/TestMoneroConnectionManager.java
index 7e14ef57..04dadd32 100644
--- a/src/test/java/TestMoneroConnectionManager.java
+++ b/src/test/java/TestMoneroConnectionManager.java
@@ -23,6 +23,30 @@
public class TestMoneroConnectionManager {
private static final int SYNC_PADDING = 1000;
+
+ @Test
+ public void testResponseTimesAfterSettingChange() {
+ class TestConnection extends MoneroRpcConnection {
+ TestConnection(String uri, long responseTime) {
+ super(uri);
+ this.isOnline = true;
+ this.isAuthenticated = true;
+ this.responseTime = responseTime;
+ }
+ @Override
+ public boolean checkConnection(long timeoutMs) {
+ return false;
+ }
+ }
+ MoneroRpcConnection slower = new TestConnection("http://localhost:18081", 2);
+ MoneroRpcConnection faster = new TestConnection("http://localhost:18082", 1);
+ MoneroConnectionManager connectionManager = new MoneroConnectionManager().addConnection(faster).setConnection(slower);
+ connectionManager.checkConnections();
+ connectionManager.checkConnections();
+ slower.setSslVerify(false); // changes the connection's hash
+ connectionManager.checkConnections();
+ assertTrue(faster == connectionManager.getConnection());
+ }
@Test
public void testConnectionManager() throws InterruptedException, IOException {
diff --git a/src/test/java/TestNativeLibrary.java b/src/test/java/TestNativeLibrary.java
index 8fc9005b..b9f1dc59 100644
--- a/src/test/java/TestNativeLibrary.java
+++ b/src/test/java/TestNativeLibrary.java
@@ -1,6 +1,17 @@
import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertNull;
+import static org.junit.jupiter.api.Assertions.assertThrows;
+import static org.junit.jupiter.api.Assertions.assertTrue;
import java.util.UUID;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+import java.util.concurrent.Future;
+import java.util.concurrent.TimeUnit;
+import monero.common.MoneroError;
+import monero.common.MoneroRpcConnection;
import monero.wallet.MoneroWallet;
import monero.wallet.MoneroWalletFull;
import monero.wallet.model.MoneroWalletConfig;
@@ -27,8 +38,71 @@ public void testKnownKeyDerivation() {
}
}
- public static void main(String[] args) {
+ @Test
+ public void testDaemonSslVerifyRoundTrip() {
+ MoneroRpcConnection connection = new MoneroRpcConnection(TestUtils.OFFLINE_SERVER_URI, "user", "password").setProxyUri("127.0.0.1:19050").setSslVerify(false);
+ MoneroWallet wallet = MoneroWalletFull.createWallet(new MoneroWalletConfig().setNetworkType(TestUtils.NETWORK_TYPE).setSeed(TestUtils.SEED).setServer(connection).setRestoreHeight(0l));
+ try {
+ assertFalse(wallet.getDaemonConnection().getSslVerify());
+ assertEquals(connection, wallet.getDaemonConnection());
+ wallet.setDaemonConnection(wallet.getDaemonConnection());
+ assertEquals(connection, wallet.getDaemonConnection());
+ assertFalse(wallet.getDaemonConnection().getSslVerify());
+ assertEquals("user", wallet.getDaemonConnection().getUsername());
+ assertEquals("password", wallet.getDaemonConnection().getPassword());
+ wallet.setDaemonConnection(connection.setSslVerify(true));
+ assertTrue(wallet.getDaemonConnection().getSslVerify());
+ wallet.setDaemonConnection(connection.setSslVerify(false));
+ assertFalse(wallet.getDaemonConnection().getSslVerify());
+ assertThrows(MoneroError.class, () -> wallet.setDaemonConnection(new MoneroRpcConnection("https://127.0.0.1:65536")));
+ assertEquals(connection, wallet.getDaemonConnection());
+ wallet.setDaemonConnection(new MoneroRpcConnection(connection).setProxyUri("127.0.0.1:19051"));
+ assertEquals("127.0.0.1:19051", wallet.getDaemonConnection().getProxyUri());
+ wallet.setDaemonConnection((MoneroRpcConnection) null);
+ assertNull(wallet.getDaemonConnection());
+ wallet.setDaemonConnection(new MoneroRpcConnection(TestUtils.OFFLINE_SERVER_URI));
+ assertTrue(wallet.getDaemonConnection().getSslVerify());
+ assertNull(wallet.getDaemonConnection().getProxyUri());
+ } finally {
+ wallet.close(false);
+ }
+ }
+
+ @Test
+ public void testConcurrentDaemonConnectionSnapshots() throws Exception {
+ MoneroRpcConnection first = new MoneroRpcConnection("http://127.0.0.1:18081", "first", "password1").setProxyUri("127.0.0.1:19050").setSslVerify(false);
+ MoneroRpcConnection second = new MoneroRpcConnection("http://127.0.0.1:18082", "second", "password2").setProxyUri("127.0.0.1:19051");
+ MoneroWallet wallet = MoneroWalletFull.createWallet(new MoneroWalletConfig().setNetworkType(TestUtils.NETWORK_TYPE));
+ ExecutorService executor = Executors.newFixedThreadPool(2);
+ CountDownLatch start = new CountDownLatch(1);
+ try {
+ wallet.setDaemonConnection(first);
+ Future>[] updates = new Future>[2];
+ MoneroRpcConnection[] connections = {first, second};
+ for (int i = 0; i < connections.length; i++) {
+ MoneroRpcConnection connection = connections[i];
+ updates[i] = executor.submit(() -> {
+ start.await();
+ for (int j = 0; j < 200; j++) {
+ wallet.setDaemonConnection(connection);
+ MoneroRpcConnection snapshot = wallet.getDaemonConnection();
+ assertTrue(first.equals(snapshot) || second.equals(snapshot));
+ }
+ return null;
+ });
+ }
+ start.countDown();
+ for (Future> update : updates) update.get(30, TimeUnit.SECONDS);
+ } finally {
+ executor.shutdownNow();
+ wallet.close(false);
+ }
+ }
+
+ public static void main(String[] args) throws Exception {
new TestNativeLibrary().testKnownKeyDerivation();
+ new TestNativeLibrary().testDaemonSslVerifyRoundTrip();
+ new TestNativeLibrary().testConcurrentDaemonConnectionSnapshots();
System.out.println("Native library verified");
}
}
diff --git a/src/test/java/TestSerialization.java b/src/test/java/TestSerialization.java
index 74dc33f0..3834cea9 100644
--- a/src/test/java/TestSerialization.java
+++ b/src/test/java/TestSerialization.java
@@ -1,15 +1,24 @@
import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertNotEquals;
+import static org.junit.jupiter.api.Assertions.assertNotSame;
+import static org.junit.jupiter.api.Assertions.assertNull;
+import static org.junit.jupiter.api.Assertions.assertTrue;
import com.fasterxml.jackson.core.type.TypeReference;
import common.utils.JsonUtils;
import java.math.BigInteger;
import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import monero.common.MoneroRpcConnection;
+import monero.common.SslOptions;
+import monero.wallet.MoneroWalletRpc;
import org.junit.jupiter.api.Test;
/**
@@ -76,4 +85,77 @@ public void testListSerializationWithCustomTypes() {
map1.remove("null"); // nulls should be removed during serialization
assertEquals(map1, map2);
}
-}
\ No newline at end of file
+
+ @Test
+ public void testConnectionSslEquality() {
+ MoneroRpcConnection connection = new MoneroRpcConnection("https://localhost:18081", "user", "password").setProxyUri("127.0.0.1:9050");
+ MoneroRpcConnection copy = new MoneroRpcConnection(connection);
+ assertEquals(connection, copy);
+ assertEquals(connection.hashCode(), copy.hashCode());
+ copy.setSslVerify(false);
+ assertNotEquals(connection, copy);
+ assertNotEquals(copy, connection);
+ connection.setSslVerify(false);
+ assertEquals(connection, copy);
+ assertEquals(connection.hashCode(), copy.hashCode());
+ }
+
+ @Test
+ public void testWalletRpcSslOptions() {
+ Map params = new HashMap();
+ MoneroWalletRpc wallet = new MoneroWalletRpc(new MoneroRpcConnection("http://localhost:18082") {
+ @Override
+ public Map sendJsonRequest(String method, Object requestParams) {
+ assertEquals("set_daemon", method);
+ params.clear();
+ params.putAll(JsonUtils.toMap(requestParams));
+ return Collections.emptyMap();
+ }
+ });
+ MoneroRpcConnection connection = new MoneroRpcConnection("https://localhost:18081");
+ wallet.setDaemonConnection(connection);
+ assertEquals(false, params.get("ssl_allow_any_cert"));
+ wallet.setDaemonConnection(connection.setSslVerify(false));
+ assertEquals(true, params.get("ssl_allow_any_cert"));
+ assertEquals("autodetect", params.get("ssl_support"));
+ wallet.setDaemonConnection(connection.setSslVerify(true));
+ assertEquals(false, params.get("ssl_allow_any_cert"));
+
+ // explicit options must not be weakened or mutated by the connection's setting
+ connection.setSslVerify(false);
+ SslOptions options = new SslOptions();
+ wallet.setDaemonConnection(connection, false, options);
+ assertNull(params.get("ssl_allow_any_cert"));
+ assertEquals("autodetect", params.get("ssl_support"));
+ assertTrue(wallet.getDaemonConnection().getSslVerify());
+ assertNotSame(connection, wallet.getDaemonConnection());
+ assertFalse(connection.getSslVerify());
+ options.setCertificateAuthorityFile("ca.pem");
+ options.setAllowedFingerprints(Arrays.asList("fingerprint"));
+ wallet.setDaemonConnection(connection, false, options);
+ assertNull(params.get("ssl_allow_any_cert"));
+ assertEquals("ca.pem", params.get("ssl_ca_file"));
+ assertEquals(options.getAllowedFingerprints(), params.get("ssl_allowed_fingerprints"));
+ assertEquals("enabled", params.get("ssl_support")); // a ca file or fingerprints must be enforced
+ assertNull(options.getAllowAnyCert());
+ options.setAllowAnyCert(false);
+ wallet.setDaemonConnection(connection, false, options);
+ assertEquals(false, params.get("ssl_allow_any_cert"));
+ assertTrue(wallet.getDaemonConnection().getSslVerify());
+ assertFalse(connection.getSslVerify());
+ wallet.setDaemonConnection(wallet.getDaemonConnection());
+ assertEquals(false, params.get("ssl_allow_any_cert"));
+ options.setAllowAnyCert(true);
+ wallet.setDaemonConnection(connection.setSslVerify(true), false, options);
+ assertEquals(true, params.get("ssl_allow_any_cert"));
+ assertEquals("autodetect", params.get("ssl_support"));
+ assertFalse(wallet.getDaemonConnection().getSslVerify());
+ assertTrue(connection.getSslVerify());
+ wallet.setDaemonConnection(wallet.getDaemonConnection());
+ assertEquals(true, params.get("ssl_allow_any_cert"));
+ wallet.setDaemonConnection((MoneroRpcConnection) null);
+ assertEquals("placeholder", params.get("address"));
+ assertNull(params.get("ssl_allow_any_cert"));
+ assertNull(wallet.getDaemonConnection());
+ }
+}