1
0
Fork 1

Finalize and fix DNSSEC and DANE verification

This commit is contained in:
12aw 2023-11-26 16:28:57 +01:00
parent 611adaabf2
commit ba211f119d
6 changed files with 188 additions and 120 deletions

View file

@ -1510,14 +1510,14 @@ public class EditAccountActivity extends OmemoActivity implements OnAccountUpdat
this.binding.verificationBox.setVisibility(View.VISIBLE);
if (mAccount.getXmppConnection() != null && mAccount.getXmppConnection().resolverAuthenticated()) {
if (mAccount.getXmppConnection().daneVerified()) {
this.binding.verificationMessage.setText("DNSSEC + DANE Verified");
this.binding.verificationMessage.setText(R.string.dnssec_dane_verified);
this.binding.verificationIndicator.setImageResource(R.drawable.shield_verified);
} else {
this.binding.verificationMessage.setText("DNSSEC Verified");
this.binding.verificationMessage.setText(R.string.dnssec_verified);
this.binding.verificationIndicator.setImageResource(R.drawable.shield);
}
} else {
this.binding.verificationMessage.setText("Not DNSSEC Verified");
this.binding.verificationMessage.setText(R.string.not_dnssec_verified);
this.binding.verificationIndicator.setImageResource(R.drawable.shield_question);
}
} else {

View file

@ -23,7 +23,6 @@ import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
//import de.gultsch.minidns.AndroidDNSClient;
import org.minidns.AbstractDnsClient;
import org.minidns.DnsCache;
import org.minidns.DnsClient;
@ -421,6 +420,43 @@ public class Resolver {
'}';
}
public void connect() {
if (this.socket != null) {
this.disconnect();
}
if (this.ip == null || this.port == 0) {
Log.d(Config.LOGTAG, "Resolver did not get IP:port (" + this.ip + ":" + this.port + ")");
return;
}
final InetSocketAddress addr = new InetSocketAddress(this.ip, this.port);
this.socket = new Socket();
try {
long time = System.currentTimeMillis();
this.socket.connect(addr, Config.SOCKET_TIMEOUT * 1000);
time = System.currentTimeMillis() - time;
if (this.logID != null && !this.logID.isEmpty()) {
Log.d(Config.LOGTAG, Resolver.class.getSimpleName() + ": Result (" + this.logID + ") connect: " + toString() + " after: " + time + " ms");
} else {
Log.d(Config.LOGTAG, Resolver.class.getSimpleName() + ": Result connect: " + toString() + " after: " + time + " ms");
}
} catch (IOException e) {
e.printStackTrace();
this.disconnect();
}
}
public void disconnect() {
if (this.socket != null) {
FileBackend.close(this.socket);
this.socket = null;
if (this.logID != null && !this.logID.isEmpty()) {
Log.d(Config.LOGTAG, Resolver.class.getSimpleName() + ": Result (" + this.logID + ") disconnect: " + toString());
} else {
Log.d(Config.LOGTAG, Resolver.class.getSimpleName() + ": Result disconnect: " + toString());
}
}
}
@Override
public int compareTo(@NonNull Result result) {
if (result.priority == priority) {
@ -444,6 +480,13 @@ public class Resolver {
}
}
public Result call() throws Exception {
this.connect();
if (this.socket != null && this.socket.isConnected()) {
return this;
}
throw new Exception("Resolver.Result was not possible to connect - should be catched by executor");
}
public static Result fromCursor(Cursor cursor) {
final Result result = new Result();

View file

@ -425,7 +425,7 @@ public class XmppConnection implements Runnable {
}
} else {
final String domain = account.getServer();
List<Resolver.Result> results;
final List<Resolver.Result> results;
final boolean hardcoded = extended && !account.getHostname().isEmpty();
if (hardcoded) {
results = Resolver.fromHardCoded(account.getHostname(), account.getPort());
@ -436,7 +436,7 @@ public class XmppConnection implements Runnable {
Log.d(Config.LOGTAG, account.getJid().asBareJid() + ": Thread was interrupted");
return;
}
if (results == null) {
if (results.size() == 0) {
Log.e(
Config.LOGTAG,
account.getJid().asBareJid() + ": Resolver results were empty");
@ -448,8 +448,8 @@ public class XmppConnection implements Runnable {
} else {
storedBackupResult =
mXmppConnectionService.databaseBackend.findResolverResult(domain);
if (storedBackupResult != null && results != storedBackupResult && !storedBackupResult.isOutdated()) {
results = (List<Resolver.Result>) storedBackupResult;
if (storedBackupResult != null && !results.contains(storedBackupResult) && !storedBackupResult.isOutdated()) {
results.add(storedBackupResult);
Log.d(
Config.LOGTAG,
account.getJid().asBareJid()
@ -464,90 +464,109 @@ public class XmppConnection implements Runnable {
}
for (final Iterator<Resolver.Result> iterator = results.iterator();
iterator.hasNext(); ) {
final Resolver.Result result = iterator.next();
if (results == null || result.getSocket() == null) {
results = Resolver.resolve(domain);
}
if (results == null) {
throw new UnknownHostException();
}
if (Thread.currentThread().isInterrupted()) {
Log.d(
Config.LOGTAG,
account.getJid().asBareJid() + ": Thread was interrupted");
return;
}
try {
// if tls is true, encryption is implied and must not be started
features.encryptionEnabled = result.isDirectTls();
verifiedHostname = result.isAuthenticated() ? result.getHostname().toString() : null;
Log.d(Config.LOGTAG, account.getJid().asBareJid().toString()
+ ": using values from resolver " + results.toString());
localSocket = result.getSocket();
if (features.encryptionEnabled) {
localSocket = upgradeSocketToTls(localSocket);
final Resolver.Result result = iterator.next();
if (Thread.currentThread().isInterrupted()) {
Log.d(
Config.LOGTAG,
account.getJid().asBareJid() + ": Thread was interrupted");
return;
}
localSocket.setSoTimeout(Config.SOCKET_TIMEOUT * 1000);
if (startXmpp(localSocket)) {
localSocket.setSoTimeout(
0); // reset to 0; once the connection is established we dont
// want this
if (!hardcoded && !result.equals(storedBackupResult)) {
mXmppConnectionService.databaseBackend.saveResolverResult(
domain, result);
try {
// if tls is true, encryption is implied and must not be started
features.encryptionEnabled = result.isDirectTls();
verifiedHostname =
result.isAuthenticated() ? result.getHostname().toString() : null;
final InetSocketAddress addr;
if (result.getIp() != null) {
addr = new InetSocketAddress(result.getIp(), result.getPort());
Log.d(
Config.LOGTAG,
account.getJid().asBareJid().toString()
+ ": using values from resolver "
+ (result.getHostname() == null
? ""
: result.getHostname().toString() + "/")
+ result.getIp().getHostAddress()
+ ":"
+ result.getPort()
+ " tls: "
+ features.encryptionEnabled);
} else {
addr =
new InetSocketAddress(
IDN.toASCII(result.getHostname().toString()),
result.getPort());
Log.d(
Config.LOGTAG,
account.getJid().asBareJid().toString()
+ ": using values from resolver "
+ result.getHostname().toString()
+ ":"
+ result.getPort()
+ " tls: "
+ features.encryptionEnabled);
}
localSocket = new Socket();
localSocket.connect(addr, Config.SOCKET_TIMEOUT * 1000);
if (features.encryptionEnabled) {
localSocket = upgradeSocketToTls(localSocket);
}
localSocket.setSoTimeout(Config.SOCKET_TIMEOUT * 1000);
if (startXmpp(localSocket)) {
localSocket.setSoTimeout(
0); // reset to 0; once the connection is established we dont
// want this
if (!hardcoded && !result.equals(storedBackupResult)) {
mXmppConnectionService.databaseBackend.saveResolverResult(
domain, result);
}
this.currentResolverResult = result;
this.seeOtherHostResolverResult = null;
break; // successfully connected to server that speaks xmpp
} else {
FileBackend.close(localSocket);
throw new StateChangingException(Account.State.STREAM_OPENING_ERROR);
}
} catch (final StateChangingException e) {
if (!iterator.hasNext()) {
throw e;
}
} catch (InterruptedException e) {
Log.d(
Config.LOGTAG,
account.getJid().asBareJid()
+ ": thread was interrupted before beginning stream");
return;
} catch (final Throwable e) {
Log.d(
Config.LOGTAG,
account.getJid().asBareJid().toString()
+ ": "
+ e.getMessage()
+ "("
+ e.getClass().getName()
+ ")");
if (!iterator.hasNext()) {
throw new UnknownHostException();
}
this.currentResolverResult = result;
this.seeOtherHostResolverResult = null;
break; // successfully connected to server that speaks xmpp
} else {
FileBackend.close(localSocket);
throw new StateChangingException(Account.State.STREAM_OPENING_ERROR);
}
} catch (final StateChangingException e) {
throw e;
} catch (InterruptedException e) {
Log.d(
Config.LOGTAG,
account.getJid().asBareJid()
+ ": thread was interrupted before beginning stream");
return;
} catch (final Throwable e) {
Log.d(
Config.LOGTAG,
account.getJid().asBareJid().toString()
+ ": "
+ e.getMessage()
+ "("
+ e.getClass().getName()
+ ")");
throw new UnknownHostException();
}
}
}
processStream();
} catch (final SecurityException e) {
this.changeStatus(Account.State.MISSING_INTERNET_PERMISSION);
} catch (final StateChangingException e) {
this.changeStatus(e.state);
} catch (final UnknownHostException
| ConnectException
| SocksSocketFactory.HostNotFoundException e) {
| ConnectException
| SocksSocketFactory.HostNotFoundException e) {
this.changeStatus(Account.State.SERVER_NOT_FOUND);
} catch (final SocksSocketFactory.SocksProxyNotFoundException e) {
if (!account.isI2P()) {
this.changeStatus(Account.State.TOR_NOT_AVAILABLE);
} else {
this.changeStatus(Account.State.I2P_NOT_AVAILABLE);
}
} catch (final IOException e) {
Log.d(Config.LOGTAG, account.getJid().asBareJid().toString() + ": " + e.getMessage());
this.changeStatus(Account.State.OFFLINE);
this.attempt = Math.max(0, this.attempt - 1);
} catch (final XmlPullParserException e) {
this.changeStatus(Account.State.TOR_NOT_AVAILABLE);
} catch (final IOException | XmlPullParserException e) {
Log.d(Config.LOGTAG, account.getJid().asBareJid().toString() + ": " + e.getMessage());
this.changeStatus(Account.State.OFFLINE);
this.attempt = Math.max(0, this.attempt - 1);
@ -1380,7 +1399,7 @@ public class XmppConnection implements Runnable {
this.dane = false;
final SSLSocketFactory sslSocketFactory;
try {
sslSocketFactory = getSSLSocketFactory(socket.getPort(), (d) -> this.dane = d);
sslSocketFactory = getSSLSocketFactory(account.getPort(), (d) -> this.dane = d);
} catch (final NoSuchAlgorithmException | KeyManagementException e) {
throw new StateChangingException(Account.State.TLS_ERROR);
}
@ -1388,7 +1407,7 @@ public class XmppConnection implements Runnable {
final SSLSocket sslSocket =
(SSLSocket)
sslSocketFactory.createSocket(
socket, address.getHostAddress(), socket.getPort(), true);
socket, address.getHostAddress(), account.getPort(), true);
SSLSockets.setSecurity(sslSocket);
SSLSockets.setHostname(sslSocket, IDN.toASCII(account.getServer()));
SSLSockets.setApplicationProtocol(sslSocket, "xmpp-client");

View file

@ -44,6 +44,7 @@
android:layout_height="wrap_content"
android:padding="@dimen/card_padding_regular">
<LinearLayout
android:id="@+id/jid_password_box"
android:layout_width="match_parent"
@ -77,7 +78,7 @@
<eu.siacs.conversations.ui.widget.TextInputEditText
android:id="@+id/hostname"
style="@style/Widget.Material3.TextInputEditText.FilledBox"
android:fontFamily="notosansregular"
android:fontFamily="notosansregular"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:inputType="textWebEmailAddress" />
@ -146,7 +147,7 @@ android:fontFamily="notosansregular"
<eu.siacs.conversations.ui.widget.TextInputEditText
android:id="@+id/account_password"
style="@style/Widget.Material3.TextInputEditText.FilledBox"
android:fontFamily="notosansregular"
android:fontFamily="notosansregular"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:hint="@string/password"
@ -155,6 +156,42 @@ android:fontFamily="notosansregular"
</com.google.android.material.textfield.TextInputLayout>
<RelativeLayout
android:id="@+id/verification_box"
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:layout_alignParentBottom="true"
android:layout_marginTop="12dp">
<LinearLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentLeft="true"
android:layout_centerVertical="true"
android:layout_toLeftOf="@+id/verification_indicator"
android:orientation="vertical">
<TextView
android:id="@+id/verification_message"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/not_dnssec_verified"
android:textAppearance="@style/TextAppearance.Conversations.Body1.Tertiary" />
</LinearLayout>
<ImageView
android:id="@+id/verification_indicator"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentRight="true"
android:layout_centerVertical="true"
android:alpha="?attr/icon_alpha"
android:background="?attr/selectableItemBackgroundBorderless"
android:padding="@dimen/image_button_padding"
android:src="@drawable/shield_question"
android:visibility="visible" />
</RelativeLayout>
<RelativeLayout
android:id="@+id/account_color_box"
android:layout_width="wrap_content"
@ -269,42 +306,6 @@ android:fontFamily="notosansregular"
android:visibility="gone" />
</LinearLayout>
<RelativeLayout
android:id="@+id/verification_box"
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:layout_marginTop="12dp">
<LinearLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentLeft="true"
android:layout_centerVertical="true"
android:layout_toLeftOf="@+id/verification_indicator"
android:orientation="vertical">
<TextView
android:id="@+id/verification_message"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Not DNSSEC Verified"
android:textAppearance="@style/TextAppearance.Conversations.Body1.Tertiary" />
</LinearLayout>
<ImageView
android:id="@+id/verification_indicator"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentRight="true"
android:layout_centerVertical="true"
android:alpha="?attr/icon_alpha"
android:background="?attr/selectableItemBackgroundBorderless"
android:padding="@dimen/image_button_padding"
android:src="@drawable/shield_question"
android:visibility="visible" />
</RelativeLayout>
<RelativeLayout
android:id="@+id/your_name_box"
android:layout_width="wrap_content"
@ -444,7 +445,6 @@ android:fontFamily="notosansregular"
android:maxHeight="@dimen/avatar_on_details_screen_size"
app:strokeColor="@color/custom_theme_accent"
app:riv_corner_radius="5dp" />
</RelativeLayout>
<com.google.android.material.materialswitch.MaterialSwitch

View file

@ -1396,4 +1396,7 @@
<string name="log_in">Anmelden</string>
<string name="contact_uses_unverified_keys">Dein Kontakt nutzt nicht verifizierte Geräte. Scanne seinen QR-Code, um ihn zu verifizieren und "MITM"-Angriffe aktiv zu verhindern.</string>
<string name="unverified_devices">Du benutzt nicht verifizierte Geräte. Scanne den QR-Code an deinen anderen Geräten um sie zu verifizieren und "MITM"-Angriffe aktiv zu verhindern.</string>
<string name="not_dnssec_verified">Nicht DNSSEC verifiziert</string>
<string name="dnssec_dane_verified">DNSSEC + DANE verifiziert</string>
<string name="dnssec_verified">DNSSEC verifiziert</string>
</resources>

View file

@ -1359,4 +1359,7 @@
<string name="security">Security</string>
<string name="verified_with_qr_code">&#160;- verified with QR-code</string>
<string name="key_automatically_accepted">&#160;- key not verified</string>
<string name="not_dnssec_verified">Not DNSSEC verified</string>
<string name="dnssec_dane_verified">DNSSEC + DANE verified</string>
<string name="dnssec_verified">DNSSEC verified</string>
</resources>