Added account/identity option to enforce DANE

pull/214/head
M66B 12 months ago
parent ea9125ff72
commit 399612896f

File diff suppressed because it is too large Load Diff

@ -583,8 +583,7 @@ public class ActivityEML extends ActivityBase {
Session isession = Session.getInstance(props, null); Session isession = Session.getInstance(props, null);
MimeMessage imessage = new MimeMessage(isession, is); MimeMessage imessage = new MimeMessage(isession, is);
try (EmailService iservice = new EmailService( try (EmailService iservice = new EmailService(context, account, EmailService.PURPOSE_USE, true)) {
context, account.getProtocol(), account.realm, account.encryption, account.insecure, account.unicode, true)) {
iservice.setPartialFetch(account.partial_fetch); iservice.setPartialFetch(account.partial_fetch);
iservice.setRawFetch(account.raw_fetch); iservice.setRawFetch(account.raw_fetch);
iservice.setIgnoreBodyStructureSize(account.ignore_size); iservice.setIgnoreBodyStructureSize(account.ignore_size);

@ -426,9 +426,7 @@ public class BoundaryCallbackMessages extends PagedList.BoundaryCallback<TupleMe
throw new IllegalStateException(context.getString(R.string.title_no_internet)); throw new IllegalStateException(context.getString(R.string.title_no_internet));
EntityLog.log(context, "Boundary server connecting account=" + account.name); EntityLog.log(context, "Boundary server connecting account=" + account.name);
state.iservice = new EmailService( state.iservice = new EmailService(context, account, EmailService.PURPOSE_SEARCH, debug || BuildConfig.DEBUG);
context, account.getProtocol(), account.realm, account.encryption, account.insecure, account.unicode,
EmailService.PURPOSE_SEARCH, debug || BuildConfig.DEBUG);
state.iservice.setPartialFetch(account.partial_fetch); state.iservice.setPartialFetch(account.partial_fetch);
state.iservice.setRawFetch(account.raw_fetch); state.iservice.setRawFetch(account.raw_fetch);
state.iservice.setIgnoreBodyStructureSize(account.ignore_size); state.iservice.setIgnoreBodyStructureSize(account.ignore_size);

@ -67,7 +67,7 @@ import javax.mail.internet.InternetAddress;
// https://developer.android.com/topic/libraries/architecture/room.html // https://developer.android.com/topic/libraries/architecture/room.html
@Database( @Database(
version = 287, version = 288,
entities = { entities = {
EntityIdentity.class, EntityIdentity.class,
EntityAccount.class, EntityAccount.class,
@ -2908,6 +2908,13 @@ public abstract class DB extends RoomDatabase {
logMigration(startVersion, endVersion); logMigration(startVersion, endVersion);
db.execSQL("ALTER TABLE `message` ADD COLUMN `auth` INTEGER"); db.execSQL("ALTER TABLE `message` ADD COLUMN `auth` INTEGER");
} }
}).addMigrations(new Migration(287, 288) {
@Override
public void migrate(@NonNull SupportSQLiteDatabase db) {
logMigration(startVersion, endVersion);
db.execSQL("ALTER TABLE `account` ADD COLUMN `dane` INTEGER NOT NULL DEFAULT 0");
db.execSQL("ALTER TABLE `identity` ADD COLUMN `dane` INTEGER NOT NULL DEFAULT 0");
}
}).addMigrations(new Migration(998, 999) { }).addMigrations(new Migration(998, 999) {
@Override @Override
public void migrate(@NonNull SupportSQLiteDatabase db) { public void migrate(@NonNull SupportSQLiteDatabase db) {

@ -99,6 +99,7 @@ public class EmailService implements AutoCloseable {
private Context context; private Context context;
private String protocol; private String protocol;
private boolean insecure; private boolean insecure;
private boolean dane;
private int purpose; private int purpose;
private boolean ssl_harden; private boolean ssl_harden;
private boolean ssl_harden_strict; private boolean ssl_harden_strict;
@ -168,14 +169,11 @@ public class EmailService implements AutoCloseable {
// Prevent instantiation // Prevent instantiation
} }
EmailService(Context context, String protocol, String realm, int encryption, boolean insecure, boolean unicode, boolean debug) throws NoSuchProviderException { EmailService(Context context, String protocol, String realm, int encryption, boolean insecure, boolean dane, boolean unicode, int purpose, boolean debug) throws NoSuchProviderException {
this(context, protocol, realm, encryption, insecure, unicode, PURPOSE_USE, debug);
}
EmailService(Context context, String protocol, String realm, int encryption, boolean insecure, boolean unicode, int purpose, boolean debug) throws NoSuchProviderException {
this.context = context.getApplicationContext(); this.context = context.getApplicationContext();
this.protocol = protocol; this.protocol = protocol;
this.insecure = insecure; this.insecure = insecure;
this.dane = dane;
this.purpose = purpose; this.purpose = purpose;
this.debug = debug; this.debug = debug;
@ -312,6 +310,14 @@ public class EmailService implements AutoCloseable {
throw new NoSuchProviderException(protocol); throw new NoSuchProviderException(protocol);
} }
EmailService(Context context, EntityIdentity ident, int purpose, boolean debug) throws NoSuchProviderException {
this(context, ident.getProtocol(), ident.realm, ident.encryption, ident.insecure, ident.dane, ident.unicode, purpose, debug);
}
EmailService(Context context, EntityAccount account, int purpose, boolean debug) throws NoSuchProviderException {
this(context, account.getProtocol(), account.realm, account.encryption, account.insecure, account.dane, account.unicode, purpose, debug);
}
void setPartialFetch(boolean enabled) { void setPartialFetch(boolean enabled) {
properties.put("mail." + protocol + ".partialfetch", Boolean.toString(enabled)); properties.put("mail." + protocol + ".partialfetch", Boolean.toString(enabled));
} }
@ -454,7 +460,7 @@ public class EmailService implements AutoCloseable {
boolean bc = prefs.getBoolean("bouncy_castle", false); boolean bc = prefs.getBoolean("bouncy_castle", false);
boolean fips = prefs.getBoolean("bc_fips", false); boolean fips = prefs.getBoolean("bc_fips", false);
factory = new SSLSocketFactoryService( factory = new SSLSocketFactoryService(
context, host, insecure, context, host, port, insecure, dane,
ssl_harden, strict, cert_strict, cert_transparency, check_names, ssl_harden, strict, cert_strict, cert_transparency, check_names,
bc, fips, key, chain, fingerprint); bc, fips, key, chain, fingerprint);
properties.put("mail." + protocol + ".ssl.socketFactory", factory); properties.put("mail." + protocol + ".ssl.socketFactory", factory);
@ -1042,8 +1048,10 @@ public class EmailService implements AutoCloseable {
private SSLSocketFactory factory; private SSLSocketFactory factory;
private X509Certificate certificate; private X509Certificate certificate;
SSLSocketFactoryService(Context context, String host, boolean insecure, SSLSocketFactoryService(Context context, String host, int port,
boolean ssl_harden, boolean ssl_harden_strict, boolean cert_strict, boolean cert_transparency, boolean check_names, boolean insecure, boolean dane,
boolean ssl_harden, boolean ssl_harden_strict, boolean cert_strict, boolean cert_transparency,
boolean check_names,
boolean bc, boolean fips, boolean bc, boolean fips,
PrivateKey key, X509Certificate[] chain, String fingerprint) throws GeneralSecurityException { PrivateKey key, X509Certificate[] chain, String fingerprint) throws GeneralSecurityException {
this.server = host; this.server = host;
@ -1053,7 +1061,7 @@ public class EmailService implements AutoCloseable {
this.trustedFingerprint = fingerprint; this.trustedFingerprint = fingerprint;
TrustManager[] tms = SSLHelper.getTrustManagers( TrustManager[] tms = SSLHelper.getTrustManagers(
context, server, secure, cert_strict, cert_transparency, check_names, trustedFingerprint, context, server, port, secure, dane, cert_strict, cert_transparency, check_names, trustedFingerprint,
new SSLHelper.ITrust() { new SSLHelper.ITrust() {
@Override @Override
public void checkServerTrusted(X509Certificate[] chain) { public void checkServerTrusted(X509Certificate[] chain) {

@ -81,6 +81,8 @@ public class EntityAccount extends EntityOrder implements Serializable {
@NonNull @NonNull
public Boolean insecure = false; public Boolean insecure = false;
@NonNull @NonNull
public Boolean dane = false;
@NonNull
public Integer port; public Integer port;
@NonNull @NonNull
public Integer auth_type; public Integer auth_type;

@ -75,6 +75,8 @@ public class EntityIdentity {
@NonNull @NonNull
public Boolean insecure = false; public Boolean insecure = false;
@NonNull @NonNull
public Boolean dane = false;
@NonNull
public Integer port; public Integer port;
@NonNull @NonNull
public Integer auth_type; public Integer auth_type;

@ -101,6 +101,7 @@ public class FragmentAccount extends FragmentBase {
private RadioGroup rgEncryption; private RadioGroup rgEncryption;
private CheckBox cbInsecure; private CheckBox cbInsecure;
private TextView tvInsecureRemark; private TextView tvInsecureRemark;
private CheckBox cbDane;
private EditText etPort; private EditText etPort;
private EditText etUser; private EditText etUser;
private TextInputLayout tilPassword; private TextInputLayout tilPassword;
@ -224,6 +225,7 @@ public class FragmentAccount extends FragmentBase {
rgEncryption = view.findViewById(R.id.rgEncryption); rgEncryption = view.findViewById(R.id.rgEncryption);
cbInsecure = view.findViewById(R.id.cbInsecure); cbInsecure = view.findViewById(R.id.cbInsecure);
tvInsecureRemark = view.findViewById(R.id.tvInsecureRemark); tvInsecureRemark = view.findViewById(R.id.tvInsecureRemark);
cbDane = view.findViewById(R.id.cbDane);
etUser = view.findViewById(R.id.etUser); etUser = view.findViewById(R.id.etUser);
tilPassword = view.findViewById(R.id.tilPassword); tilPassword = view.findViewById(R.id.tilPassword);
tvAppPassword = view.findViewById(R.id.tvAppPassword); tvAppPassword = view.findViewById(R.id.tvAppPassword);
@ -366,6 +368,13 @@ public class FragmentAccount extends FragmentBase {
} }
}); });
cbInsecure.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() {
@Override
public void onCheckedChanged(CompoundButton buttonView, boolean checked) {
cbDane.setEnabled(!checked);
}
});
tvInsecureRemark.setOnClickListener(new View.OnClickListener() { tvInsecureRemark.setOnClickListener(new View.OnClickListener() {
@Override @Override
public void onClick(View v) { public void onClick(View v) {
@ -631,6 +640,7 @@ public class FragmentAccount extends FragmentBase {
if (!SSLHelper.customTrustManager()) { if (!SSLHelper.customTrustManager()) {
Helper.hide(cbInsecure); Helper.hide(cbInsecure);
Helper.hide(tvInsecureRemark); Helper.hide(tvInsecureRemark);
Helper.hide(cbDane);
} }
if (id < 0) if (id < 0)
@ -729,6 +739,7 @@ public class FragmentAccount extends FragmentBase {
args.putString("host", etHost.getText().toString().trim().replace(" ", "")); args.putString("host", etHost.getText().toString().trim().replace(" ", ""));
args.putInt("encryption", encryption); args.putInt("encryption", encryption);
args.putBoolean("insecure", cbInsecure.isChecked()); args.putBoolean("insecure", cbInsecure.isChecked());
args.putBoolean("dane", cbDane.isChecked());
args.putString("port", etPort.getText().toString()); args.putString("port", etPort.getText().toString());
args.putInt("auth", auth); args.putInt("auth", auth);
args.putString("provider", provider); args.putString("provider", provider);
@ -774,6 +785,7 @@ public class FragmentAccount extends FragmentBase {
String host = args.getString("host"); String host = args.getString("host");
int encryption = args.getInt("encryption"); int encryption = args.getInt("encryption");
boolean insecure = args.getBoolean("insecure"); boolean insecure = args.getBoolean("insecure");
boolean dane = args.getBoolean("dane");
String port = args.getString("port"); String port = args.getString("port");
int auth = args.getInt("auth"); int auth = args.getInt("auth");
String provider = args.getString("provider"); String provider = args.getString("provider");
@ -808,8 +820,8 @@ public class FragmentAccount extends FragmentBase {
// Check IMAP server / get folders // Check IMAP server / get folders
String protocol = "imap" + (encryption == EmailService.ENCRYPTION_SSL ? "s" : ""); String protocol = "imap" + (encryption == EmailService.ENCRYPTION_SSL ? "s" : "");
try (EmailService iservice = new EmailService( try (EmailService iservice = new EmailService(context,
context, protocol, realm, encryption, insecure, unicode, protocol, realm, encryption, insecure, dane, unicode,
EmailService.PURPOSE_CHECK, true)) { EmailService.PURPOSE_CHECK, true)) {
iservice.connect( iservice.connect(
host, Integer.parseInt(port), host, Integer.parseInt(port),
@ -938,6 +950,7 @@ public class FragmentAccount extends FragmentBase {
args.putString("host", etHost.getText().toString().trim().replace(" ", "")); args.putString("host", etHost.getText().toString().trim().replace(" ", ""));
args.putInt("encryption", encryption); args.putInt("encryption", encryption);
args.putBoolean("insecure", cbInsecure.isChecked()); args.putBoolean("insecure", cbInsecure.isChecked());
args.putBoolean("dane", cbDane.isChecked());
args.putString("port", etPort.getText().toString()); args.putString("port", etPort.getText().toString());
args.putInt("auth", auth); args.putInt("auth", auth);
args.putString("provider", provider); args.putString("provider", provider);
@ -1015,6 +1028,7 @@ public class FragmentAccount extends FragmentBase {
String host = args.getString("host"); String host = args.getString("host");
int encryption = args.getInt("encryption"); int encryption = args.getInt("encryption");
boolean insecure = args.getBoolean("insecure"); boolean insecure = args.getBoolean("insecure");
boolean dane = args.getBoolean("dane");
String port = args.getString("port"); String port = args.getString("port");
int auth = args.getInt("auth"); int auth = args.getInt("auth");
String provider = args.getString("provider"); String provider = args.getString("provider");
@ -1111,6 +1125,8 @@ public class FragmentAccount extends FragmentBase {
return true; return true;
if (!Objects.equals(account.insecure, insecure)) if (!Objects.equals(account.insecure, insecure))
return true; return true;
if (!Objects.equals(account.dane, dane))
return true;
if (!Objects.equals(account.port, Integer.parseInt(port))) if (!Objects.equals(account.port, Integer.parseInt(port)))
return true; return true;
if (account.auth_type != auth) if (account.auth_type != auth)
@ -1211,6 +1227,7 @@ public class FragmentAccount extends FragmentBase {
!account.host.equals(host) || !account.host.equals(host) ||
!account.encryption.equals(encryption) || !account.encryption.equals(encryption) ||
!account.insecure.equals(insecure) || !account.insecure.equals(insecure) ||
!account.dane.equals(dane) ||
!account.port.equals(Integer.parseInt(port)) || !account.port.equals(Integer.parseInt(port)) ||
!account.user.equals(user) || !account.user.equals(user) ||
!account.password.equals(password) || !account.password.equals(password) ||
@ -1228,8 +1245,8 @@ public class FragmentAccount extends FragmentBase {
EntityFolder inbox = null; EntityFolder inbox = null;
if (check) { if (check) {
String protocol = "imap" + (encryption == EmailService.ENCRYPTION_SSL ? "s" : ""); String protocol = "imap" + (encryption == EmailService.ENCRYPTION_SSL ? "s" : "");
try (EmailService iservice = new EmailService( try (EmailService iservice = new EmailService(context,
context, protocol, realm, encryption, insecure, unicode, protocol, realm, encryption, insecure, dane, unicode,
EmailService.PURPOSE_CHECK, true)) { EmailService.PURPOSE_CHECK, true)) {
iservice.connect( iservice.connect(
host, Integer.parseInt(port), host, Integer.parseInt(port),
@ -1277,6 +1294,7 @@ public class FragmentAccount extends FragmentBase {
account.host = host; account.host = host;
account.encryption = encryption; account.encryption = encryption;
account.insecure = insecure; account.insecure = insecure;
account.dane = dane;
account.port = Integer.parseInt(port); account.port = Integer.parseInt(port);
account.auth_type = auth; account.auth_type = auth;
account.user = user; account.user = user;
@ -1673,6 +1691,8 @@ public class FragmentAccount extends FragmentBase {
rgEncryption.check(R.id.radio_ssl); rgEncryption.check(R.id.radio_ssl);
cbInsecure.setChecked(account == null ? false : account.insecure); cbInsecure.setChecked(account == null ? false : account.insecure);
cbDane.setChecked(account == null ? false : account.dane);
cbDane.setEnabled(!cbInsecure.isChecked());
etUser.setText(account == null ? null : account.user); etUser.setText(account == null ? null : account.user);
tilPassword.getEditText().setText(account == null ? null : account.password); tilPassword.getEditText().setText(account == null ? null : account.password);

@ -479,8 +479,8 @@ public class FragmentGmail extends FragmentBase {
EmailProvider.Server inbound = (pop ? provider.pop : provider.imap); EmailProvider.Server inbound = (pop ? provider.pop : provider.imap);
String aprotocol = (pop ? (inbound.starttls ? "pop3" : "pop3s") : (inbound.starttls ? "imap" : "imaps")); String aprotocol = (pop ? (inbound.starttls ? "pop3" : "pop3s") : (inbound.starttls ? "imap" : "imaps"));
int aencryption = (inbound.starttls ? EmailService.ENCRYPTION_STARTTLS : EmailService.ENCRYPTION_SSL); int aencryption = (inbound.starttls ? EmailService.ENCRYPTION_STARTTLS : EmailService.ENCRYPTION_SSL);
try (EmailService aservice = new EmailService( try (EmailService aservice = new EmailService(context,
context, aprotocol, null, aencryption, false, false, aprotocol, null, aencryption, false, false, false,
EmailService.PURPOSE_CHECK, true)) { EmailService.PURPOSE_CHECK, true)) {
aservice.connect( aservice.connect(
inbound.host, inbound.port, inbound.host, inbound.port,
@ -497,8 +497,8 @@ public class FragmentGmail extends FragmentBase {
Long max_size; Long max_size;
String iprotocol = (provider.smtp.starttls ? "smtp" : "smtps"); String iprotocol = (provider.smtp.starttls ? "smtp" : "smtps");
int iencryption = (provider.smtp.starttls ? EmailService.ENCRYPTION_STARTTLS : EmailService.ENCRYPTION_SSL); int iencryption = (provider.smtp.starttls ? EmailService.ENCRYPTION_STARTTLS : EmailService.ENCRYPTION_SSL);
try (EmailService iservice = new EmailService( try (EmailService iservice = new EmailService(context,
context, iprotocol, null, iencryption, false, false, iprotocol, null, iencryption, false, false, false,
EmailService.PURPOSE_CHECK, true)) { EmailService.PURPOSE_CHECK, true)) {
iservice.connect( iservice.connect(
provider.smtp.host, provider.smtp.port, provider.smtp.host, provider.smtp.port,

@ -102,6 +102,7 @@ public class FragmentIdentity extends FragmentBase {
private RadioGroup rgEncryption; private RadioGroup rgEncryption;
private CheckBox cbInsecure; private CheckBox cbInsecure;
private TextView tvInsecureRemark; private TextView tvInsecureRemark;
private CheckBox cbDane;
private EditText etPort; private EditText etPort;
private EditText etUser; private EditText etUser;
private TextInputLayout tilPassword; private TextInputLayout tilPassword;
@ -209,6 +210,7 @@ public class FragmentIdentity extends FragmentBase {
rgEncryption = view.findViewById(R.id.rgEncryption); rgEncryption = view.findViewById(R.id.rgEncryption);
cbInsecure = view.findViewById(R.id.cbInsecure); cbInsecure = view.findViewById(R.id.cbInsecure);
tvInsecureRemark = view.findViewById(R.id.tvInsecureRemark); tvInsecureRemark = view.findViewById(R.id.tvInsecureRemark);
cbDane = view.findViewById(R.id.cbDane);
etPort = view.findViewById(R.id.etPort); etPort = view.findViewById(R.id.etPort);
etUser = view.findViewById(R.id.etUser); etUser = view.findViewById(R.id.etUser);
tilPassword = view.findViewById(R.id.tilPassword); tilPassword = view.findViewById(R.id.tilPassword);
@ -466,6 +468,13 @@ public class FragmentIdentity extends FragmentBase {
} }
}); });
cbInsecure.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() {
@Override
public void onCheckedChanged(CompoundButton buttonView, boolean checked) {
cbDane.setEnabled(!checked);
}
});
tvInsecureRemark.setOnClickListener(new View.OnClickListener() { tvInsecureRemark.setOnClickListener(new View.OnClickListener() {
@Override @Override
public void onClick(View v) { public void onClick(View v) {
@ -570,6 +579,7 @@ public class FragmentIdentity extends FragmentBase {
if (!SSLHelper.customTrustManager()) { if (!SSLHelper.customTrustManager()) {
Helper.hide(cbInsecure); Helper.hide(cbInsecure);
Helper.hide(tvInsecureRemark); Helper.hide(tvInsecureRemark);
Helper.hide(cbDane);
} }
btnAdvanced.setVisibility(View.GONE); btnAdvanced.setVisibility(View.GONE);
@ -768,6 +778,7 @@ public class FragmentIdentity extends FragmentBase {
args.putString("host", etHost.getText().toString().trim().replace(" ", "")); args.putString("host", etHost.getText().toString().trim().replace(" ", ""));
args.putInt("encryption", encryption); args.putInt("encryption", encryption);
args.putBoolean("insecure", cbInsecure.isChecked()); args.putBoolean("insecure", cbInsecure.isChecked());
args.putBoolean("dane", cbDane.isChecked());
args.putString("port", etPort.getText().toString()); args.putString("port", etPort.getText().toString());
args.putInt("auth", auth); args.putInt("auth", auth);
args.putString("provider", provider); args.putString("provider", provider);
@ -821,6 +832,7 @@ public class FragmentIdentity extends FragmentBase {
String host = args.getString("host"); String host = args.getString("host");
int encryption = args.getInt("encryption"); int encryption = args.getInt("encryption");
boolean insecure = args.getBoolean("insecure"); boolean insecure = args.getBoolean("insecure");
boolean dane = args.getBoolean("dane");
String port = args.getString("port"); String port = args.getString("port");
int auth = args.getInt("auth"); int auth = args.getInt("auth");
String provider = args.getString("provider"); String provider = args.getString("provider");
@ -967,6 +979,8 @@ public class FragmentIdentity extends FragmentBase {
return true; return true;
if (!Objects.equals(identity.insecure, insecure)) if (!Objects.equals(identity.insecure, insecure))
return true; return true;
if (!Objects.equals(identity.dane, dane))
return true;
if (!Objects.equals(identity.port, Integer.parseInt(port))) if (!Objects.equals(identity.port, Integer.parseInt(port)))
return true; return true;
if (identity.auth_type != auth) if (identity.auth_type != auth)
@ -1036,6 +1050,7 @@ public class FragmentIdentity extends FragmentBase {
!host.equals(identity.host) || !host.equals(identity.host) ||
encryption != identity.encryption || encryption != identity.encryption ||
insecure != identity.insecure || insecure != identity.insecure ||
dane != identity.dane ||
Integer.parseInt(port) != identity.port || Integer.parseInt(port) != identity.port ||
!user.equals(identity.user) || !user.equals(identity.user) ||
!password.equals(identity.password) || !password.equals(identity.password) ||
@ -1057,8 +1072,8 @@ public class FragmentIdentity extends FragmentBase {
if (check) { if (check) {
// Create transport // Create transport
String protocol = (encryption == EmailService.ENCRYPTION_SSL ? "smtps" : "smtp"); String protocol = (encryption == EmailService.ENCRYPTION_SSL ? "smtps" : "smtp");
try (EmailService iservice = new EmailService( try (EmailService iservice = new EmailService(context,
context, protocol, realm, encryption, insecure, unicode, protocol, realm, encryption, insecure, dane, unicode,
EmailService.PURPOSE_CHECK, true)) { EmailService.PURPOSE_CHECK, true)) {
iservice.setUseIp(use_ip, ehlo); iservice.setUseIp(use_ip, ehlo);
iservice.connect( iservice.connect(
@ -1094,6 +1109,7 @@ public class FragmentIdentity extends FragmentBase {
identity.host = host; identity.host = host;
identity.encryption = encryption; identity.encryption = encryption;
identity.insecure = insecure; identity.insecure = insecure;
identity.dane = dane;
identity.port = Integer.parseInt(port); identity.port = Integer.parseInt(port);
identity.auth_type = auth; identity.auth_type = auth;
identity.user = user; identity.user = user;
@ -1274,6 +1290,8 @@ public class FragmentIdentity extends FragmentBase {
rgEncryption.check(R.id.radio_ssl); rgEncryption.check(R.id.radio_ssl);
cbInsecure.setChecked(identity == null ? false : identity.insecure); cbInsecure.setChecked(identity == null ? false : identity.insecure);
cbDane.setChecked(identity == null ? false : identity.dane);
cbDane.setEnabled(!cbInsecure.isChecked());
etPort.setText(identity == null ? null : Long.toString(identity.port)); etPort.setText(identity == null ? null : Long.toString(identity.port));
etUser.setText(identity == null ? null : identity.user); etUser.setText(identity == null ? null : identity.user);
tilPassword.getEditText().setText(identity == null ? null : identity.password); tilPassword.getEditText().setText(identity == null ? null : identity.password);

@ -817,8 +817,8 @@ public class FragmentOAuth extends FragmentBase {
for (String alt : usernames) { for (String alt : usernames) {
EntityLog.log(context, "Trying username=" + alt); EntityLog.log(context, "Trying username=" + alt);
try { try {
try (EmailService aservice = new EmailService( try (EmailService aservice = new EmailService(context,
context, aprotocol, null, aencryption, false, false, aprotocol, null, aencryption, false, false, false,
EmailService.PURPOSE_CHECK, true)) { EmailService.PURPOSE_CHECK, true)) {
aservice.connect( aservice.connect(
inbound.host, inbound.port, inbound.host, inbound.port,
@ -827,8 +827,8 @@ public class FragmentOAuth extends FragmentBase {
null, null); null, null);
} }
if (state.length == 1) { if (state.length == 1) {
try (EmailService iservice = new EmailService( try (EmailService iservice = new EmailService(context,
context, iprotocol, null, iencryption, false, false, iprotocol, null, iencryption, false, false, false,
EmailService.PURPOSE_CHECK, true)) { EmailService.PURPOSE_CHECK, true)) {
iservice.connect( iservice.connect(
provider.smtp.host, provider.smtp.port, provider.smtp.host, provider.smtp.port,
@ -892,8 +892,8 @@ public class FragmentOAuth extends FragmentBase {
List<EntityFolder> folders; List<EntityFolder> folders;
EntityLog.log(context, "OAuth checking IMAP/POP3 provider=" + provider.id); EntityLog.log(context, "OAuth checking IMAP/POP3 provider=" + provider.id);
try (EmailService aservice = new EmailService( try (EmailService aservice = new EmailService(context,
context, aprotocol, null, aencryption, false, false, aprotocol, null, aencryption, false, false, false,
EmailService.PURPOSE_CHECK, true)) { EmailService.PURPOSE_CHECK, true)) {
aservice.connect( aservice.connect(
inbound.host, inbound.port, inbound.host, inbound.port,
@ -911,8 +911,8 @@ public class FragmentOAuth extends FragmentBase {
if (!inbound_only && state.length == 1) { if (!inbound_only && state.length == 1) {
EntityLog.log(context, "OAuth checking SMTP provider=" + provider.id); EntityLog.log(context, "OAuth checking SMTP provider=" + provider.id);
try (EmailService iservice = new EmailService( try (EmailService iservice = new EmailService(context,
context, iprotocol, null, iencryption, false, false, iprotocol, null, iencryption, false, false, false,
EmailService.PURPOSE_CHECK, true)) { EmailService.PURPOSE_CHECK, true)) {
iservice.connect( iservice.connect(
provider.smtp.host, provider.smtp.port, provider.smtp.host, provider.smtp.port,

@ -76,6 +76,7 @@ public class FragmentPop extends FragmentBase {
private RadioGroup rgEncryption; private RadioGroup rgEncryption;
private CheckBox cbInsecure; private CheckBox cbInsecure;
private TextView tvInsecureRemark; private TextView tvInsecureRemark;
private CheckBox cbDane;
private EditText etPort; private EditText etPort;
private EditText etUser; private EditText etUser;
private TextInputLayout tilPassword; private TextInputLayout tilPassword;
@ -156,6 +157,7 @@ public class FragmentPop extends FragmentBase {
rgEncryption = view.findViewById(R.id.rgEncryption); rgEncryption = view.findViewById(R.id.rgEncryption);
cbInsecure = view.findViewById(R.id.cbInsecure); cbInsecure = view.findViewById(R.id.cbInsecure);
tvInsecureRemark = view.findViewById(R.id.tvInsecureRemark); tvInsecureRemark = view.findViewById(R.id.tvInsecureRemark);
cbDane = view.findViewById(R.id.cbDane);
etUser = view.findViewById(R.id.etUser); etUser = view.findViewById(R.id.etUser);
tilPassword = view.findViewById(R.id.tilPassword); tilPassword = view.findViewById(R.id.tilPassword);
tvPasswordStorage = view.findViewById(R.id.tvPasswordStorage); tvPasswordStorage = view.findViewById(R.id.tvPasswordStorage);
@ -207,6 +209,13 @@ public class FragmentPop extends FragmentBase {
} }
}); });
cbInsecure.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() {
@Override
public void onCheckedChanged(CompoundButton buttonView, boolean checked) {
cbDane.setEnabled(!checked);
}
});
tvInsecureRemark.setOnClickListener(new View.OnClickListener() { tvInsecureRemark.setOnClickListener(new View.OnClickListener() {
@Override @Override
public void onClick(View v) { public void onClick(View v) {
@ -356,6 +365,7 @@ public class FragmentPop extends FragmentBase {
if (!SSLHelper.customTrustManager()) { if (!SSLHelper.customTrustManager()) {
Helper.hide(cbInsecure); Helper.hide(cbInsecure);
Helper.hide(tvInsecureRemark); Helper.hide(tvInsecureRemark);
Helper.hide(cbDane);
} }
if (id < 0) if (id < 0)
@ -384,6 +394,7 @@ public class FragmentPop extends FragmentBase {
args.putString("host", etHost.getText().toString().trim().replace(" ", "")); args.putString("host", etHost.getText().toString().trim().replace(" ", ""));
args.putInt("encryption", encryption); args.putInt("encryption", encryption);
args.putBoolean("insecure", cbInsecure.isChecked()); args.putBoolean("insecure", cbInsecure.isChecked());
args.putBoolean("dane", cbDane.isChecked());
args.putString("port", etPort.getText().toString()); args.putString("port", etPort.getText().toString());
args.putInt("auth", auth); args.putInt("auth", auth);
args.putString("user", etUser.getText().toString()); args.putString("user", etUser.getText().toString());
@ -441,6 +452,7 @@ public class FragmentPop extends FragmentBase {
String host = args.getString("host"); String host = args.getString("host");
int encryption = args.getInt("encryption"); int encryption = args.getInt("encryption");
boolean insecure = args.getBoolean("insecure"); boolean insecure = args.getBoolean("insecure");
boolean dane = args.getBoolean("dane");
String port = args.getString("port"); String port = args.getString("port");
int auth = args.getInt("auth"); int auth = args.getInt("auth");
String user = args.getString("user").trim(); String user = args.getString("user").trim();
@ -526,6 +538,8 @@ public class FragmentPop extends FragmentBase {
return true; return true;
if (!Objects.equals(account.insecure, insecure)) if (!Objects.equals(account.insecure, insecure))
return true; return true;
if (!Objects.equals(account.dane, dane))
return true;
if (!Objects.equals(account.port, Integer.parseInt(port))) if (!Objects.equals(account.port, Integer.parseInt(port)))
return true; return true;
if (!Objects.equals(account.user, user)) if (!Objects.equals(account.user, user))
@ -585,6 +599,7 @@ public class FragmentPop extends FragmentBase {
!account.host.equals(host) || !account.host.equals(host) ||
!account.encryption.equals(encryption) || !account.encryption.equals(encryption) ||
!account.insecure.equals(insecure) || !account.insecure.equals(insecure) ||
!account.dane.equals(dane) ||
!account.port.equals(Integer.parseInt(port)) || !account.port.equals(Integer.parseInt(port)) ||
!account.user.equals(user) || !account.user.equals(user) ||
!account.password.equals(password) || !account.password.equals(password) ||
@ -598,8 +613,8 @@ public class FragmentPop extends FragmentBase {
// Check POP3 server // Check POP3 server
if (check) { if (check) {
String protocol = "pop3" + (encryption == EmailService.ENCRYPTION_SSL ? "s" : ""); String protocol = "pop3" + (encryption == EmailService.ENCRYPTION_SSL ? "s" : "");
try (EmailService iservice = new EmailService( try (EmailService iservice = new EmailService(context,
context, protocol, null, encryption, insecure, false, protocol, null, encryption, insecure, dane, false,
EmailService.PURPOSE_CHECK, true)) { EmailService.PURPOSE_CHECK, true)) {
iservice.connect( iservice.connect(
host, Integer.parseInt(port), host, Integer.parseInt(port),
@ -629,6 +644,7 @@ public class FragmentPop extends FragmentBase {
account.host = host; account.host = host;
account.encryption = encryption; account.encryption = encryption;
account.insecure = insecure; account.insecure = insecure;
account.dane = dane;
account.port = Integer.parseInt(port); account.port = Integer.parseInt(port);
account.auth_type = auth; account.auth_type = auth;
account.user = user; account.user = user;
@ -832,6 +848,8 @@ public class FragmentPop extends FragmentBase {
rgEncryption.check(R.id.radio_ssl); rgEncryption.check(R.id.radio_ssl);
cbInsecure.setChecked(account == null ? false : account.insecure); cbInsecure.setChecked(account == null ? false : account.insecure);
cbDane.setChecked(account == null ? false : account.dane);
cbDane.setEnabled(!cbInsecure.isChecked());
etUser.setText(account == null ? null : account.user); etUser.setText(account == null ? null : account.user);
tilPassword.getEditText().setText(account == null ? null : account.password); tilPassword.getEditText().setText(account == null ? null : account.password);

@ -430,8 +430,8 @@ public class FragmentQuickSetup extends FragmentBase {
String user = null; String user = null;
String aprotocol = (provider.imap.starttls ? "imap" : "imaps"); String aprotocol = (provider.imap.starttls ? "imap" : "imaps");
int aencryption = (provider.imap.starttls ? EmailService.ENCRYPTION_STARTTLS : EmailService.ENCRYPTION_SSL); int aencryption = (provider.imap.starttls ? EmailService.ENCRYPTION_STARTTLS : EmailService.ENCRYPTION_SSL);
try (EmailService iservice = new EmailService( try (EmailService iservice = new EmailService(context,
context, aprotocol, null, aencryption, false, false, aprotocol, null, aencryption, false, false, false,
EmailService.PURPOSE_CHECK, true)) { EmailService.PURPOSE_CHECK, true)) {
List<Throwable> exceptions = new ArrayList<>(); List<Throwable> exceptions = new ArrayList<>();
for (int i = 0; i < users.size(); i++) { for (int i = 0; i < users.size(); i++) {
@ -539,8 +539,8 @@ public class FragmentQuickSetup extends FragmentBase {
Long max_size; Long max_size;
String iprotocol = (provider.smtp.starttls ? "smtp" : "smtps"); String iprotocol = (provider.smtp.starttls ? "smtp" : "smtps");
int iencryption = (provider.smtp.starttls ? EmailService.ENCRYPTION_STARTTLS : EmailService.ENCRYPTION_SSL); int iencryption = (provider.smtp.starttls ? EmailService.ENCRYPTION_STARTTLS : EmailService.ENCRYPTION_SSL);
try (EmailService iservice = new EmailService( try (EmailService iservice = new EmailService(context,
context, iprotocol, null, iencryption, false, false, iprotocol, null, iencryption, false, false, false,
EmailService.PURPOSE_CHECK, true)) { EmailService.PURPOSE_CHECK, true)) {
iservice.setUseIp(provider.useip, null); iservice.setUseIp(provider.useip, null);
try { try {

@ -29,6 +29,8 @@ import com.appmattus.certificatetransparency.CTTrustManagerBuilder;
import com.appmattus.certificatetransparency.VerificationResult; import com.appmattus.certificatetransparency.VerificationResult;
import com.appmattus.certificatetransparency.cache.AndroidDiskCache; import com.appmattus.certificatetransparency.cache.AndroidDiskCache;
import org.minidns.dane.DaneVerifier;
import java.net.InetAddress; import java.net.InetAddress;
import java.net.UnknownHostException; import java.net.UnknownHostException;
import java.security.KeyStore; import java.security.KeyStore;
@ -39,6 +41,9 @@ import java.security.cert.CertificateExpiredException;
import java.security.cert.X509Certificate; import java.security.cert.X509Certificate;
import java.util.Arrays; import java.util.Arrays;
import java.util.List; import java.util.List;
import java.util.logging.Handler;
import java.util.logging.LogRecord;
import java.util.logging.Logger;
import javax.net.ssl.TrustManager; import javax.net.ssl.TrustManager;
import javax.net.ssl.TrustManagerFactory; import javax.net.ssl.TrustManagerFactory;
@ -46,7 +51,10 @@ import javax.net.ssl.X509TrustManager;
public class SSLHelper { public class SSLHelper {
static TrustManager[] getTrustManagers( static TrustManager[] getTrustManagers(
Context context, String server, boolean secure, boolean cert_strict, boolean transparency, boolean check_names, String trustedFingerprint, ITrust intf) { Context context, String server, int port,
boolean secure, boolean dane, boolean cert_strict, boolean transparency, boolean check_names,
String trustedFingerprint,
ITrust intf) {
TrustManagerFactory tmf; TrustManagerFactory tmf;
try { try {
tmf = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm()); tmf = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());
@ -122,6 +130,31 @@ public class SSLHelper {
throw new CertificateException(principal.getName(), ex); throw new CertificateException(principal.getName(), ex);
} }
if (dane) {
Handler handler = new Handler() {
@Override
public void publish(LogRecord record) {
Log.w("DANE " + record.getMessage());
}
@Override
public void flush() {
}
@Override
public void close() throws SecurityException {
}
};
String clazz = DaneVerifier.class.getName();
Logger.getLogger(clazz).addHandler(handler);
Log.w("DANE verify " + server + ":" + port);
boolean verified = new DaneVerifier().verifyCertificateChain(chain, server, port);
Log.w("DANE verified=" + verified + " " + server + ":" + port);
Logger.getLogger(clazz).removeHandler(handler);
if (!verified)
throw new CertificateException("DANE missing or invalid");
}
// Check host name // Check host name
if (check_names) { if (check_names) {
List<String> names = EntityCertificate.getDnsNames(chain[0]); List<String> names = EntityCertificate.getDnsNames(chain[0]);

@ -752,8 +752,7 @@ public class ServiceSend extends ServiceBase implements SharedPreferences.OnShar
MicrosoftGraph.send(ServiceSend.this, ident, imessage); MicrosoftGraph.send(ServiceSend.this, ident, imessage);
end = new Date().getTime(); end = new Date().getTime();
} else { } else {
EmailService iservice = new EmailService( EmailService iservice = new EmailService(this, ident, EmailService.PURPOSE_USE, debug);
this, ident.getProtocol(), ident.realm, ident.encryption, ident.insecure, ident.unicode, debug);
try { try {
iservice.setUseIp(ident.use_ip, ident.ehlo); iservice.setUseIp(ident.use_ip, ident.ehlo);
if (!message.isSigned() && !message.isEncrypted()) if (!message.isSigned() && !message.isEncrypted())

@ -1591,8 +1591,7 @@ public class ServiceSynchronize extends ServiceBase implements SharedPreferences
boolean empty_pool = prefs.getBoolean("empty_pool", true); boolean empty_pool = prefs.getBoolean("empty_pool", true);
boolean debug = (prefs.getBoolean("debug", false) || BuildConfig.DEBUG); boolean debug = (prefs.getBoolean("debug", false) || BuildConfig.DEBUG);
final EmailService iservice = new EmailService( final EmailService iservice = new EmailService(this, account, EmailService.PURPOSE_USE, debug);
this, account.getProtocol(), account.realm, account.encryption, account.insecure, account.unicode, debug);
iservice.setPartialFetch(account.partial_fetch); iservice.setPartialFetch(account.partial_fetch);
iservice.setRawFetch(account.raw_fetch); iservice.setRawFetch(account.raw_fetch);
iservice.setIgnoreBodyStructureSize(account.ignore_size); iservice.setIgnoreBodyStructureSize(account.ignore_size);

@ -204,6 +204,29 @@
app:layout_constraintStart_toStartOf="parent" app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/cbInsecure" /> app:layout_constraintTop_toBottomOf="@id/cbInsecure" />
<CheckBox
android:id="@+id/cbDane"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="12dp"
android:layout_marginTop="12dp"
android:text="@string/title_dane"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/tvInsecureRemark" />
<TextView
android:id="@+id/tvDaneRemark"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="12dp"
android:layout_marginTop="6dp"
android:text="@string/title_dane_remark"
android:textAppearance="@style/TextAppearance.AppCompat.Small"
android:textStyle="italic"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/cbDane" />
<!-- port --> <!-- port -->
<TextView <TextView
@ -214,7 +237,7 @@
android:text="@string/title_port" android:text="@string/title_port"
android:textAppearance="@style/TextAppearance.AppCompat.Small" android:textAppearance="@style/TextAppearance.AppCompat.Small"
app:layout_constraintStart_toStartOf="parent" app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/tvInsecureRemark" /> app:layout_constraintTop_toBottomOf="@id/tvDaneRemark" />
<eu.faircode.email.EditTextPlain <eu.faircode.email.EditTextPlain
android:id="@+id/etPort" android:id="@+id/etPort"
@ -1178,7 +1201,7 @@
android:layout_height="0dp" android:layout_height="0dp"
app:constraint_referenced_ids=" app:constraint_referenced_ids="
tvDomain,tvDomainHint,etDomain,btnAutoConfig, tvDomain,tvDomainHint,etDomain,btnAutoConfig,
tvImap,tvHost,etHost,tvEncryption,rgEncryption,cbInsecure,tvInsecureRemark,tvPort,etPort" /> tvImap,tvHost,etHost,tvEncryption,rgEncryption,cbInsecure,tvInsecureRemark,cbDane,tvDaneRemark,tvPort,etPort" />
<androidx.constraintlayout.widget.Group <androidx.constraintlayout.widget.Group
android:id="@+id/grpAuthorize" android:id="@+id/grpAuthorize"

@ -355,6 +355,29 @@
app:layout_constraintStart_toStartOf="parent" app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/cbInsecure" /> app:layout_constraintTop_toBottomOf="@id/cbInsecure" />
<CheckBox
android:id="@+id/cbDane"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="12dp"
android:layout_marginTop="12dp"
android:text="@string/title_dane"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/tvInsecureRemark" />
<TextView
android:id="@+id/tvDaneRemark"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="12dp"
android:layout_marginTop="6dp"
android:text="@string/title_dane_remark"
android:textAppearance="@style/TextAppearance.AppCompat.Small"
android:textStyle="italic"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/cbDane" />
<!-- port --> <!-- port -->
<TextView <TextView
@ -365,7 +388,7 @@
android:text="@string/title_port" android:text="@string/title_port"
android:textAppearance="@style/TextAppearance.AppCompat.Small" android:textAppearance="@style/TextAppearance.AppCompat.Small"
app:layout_constraintStart_toStartOf="parent" app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/tvInsecureRemark" /> app:layout_constraintTop_toBottomOf="@id/tvDaneRemark" />
<eu.faircode.email.EditTextPlain <eu.faircode.email.EditTextPlain
android:id="@+id/etPort" android:id="@+id/etPort"
@ -1075,7 +1098,7 @@
app:constraint_referenced_ids=" app:constraint_referenced_ids="
tvProvider,spProvider, tvProvider,spProvider,
tvDomain,tvDomainHint,etDomain,btnAutoConfig, tvDomain,tvDomainHint,etDomain,btnAutoConfig,
tvSmtp,tvHost,etHost,tvEncryption,rgEncryption,cbInsecure,tvInsecureRemark,tvPort,etPort, tvSmtp,tvHost,etHost,tvEncryption,rgEncryption,cbInsecure,tvInsecureRemark,cbDane,tvDaneRemark,tvPort,etPort,
tvUser,etUser,tvPassword,tilPassword,tvCaseSensitiveHint,tvPasswordStorage, tvUser,etUser,tvPassword,tilPassword,tvCaseSensitiveHint,tvPasswordStorage,
btnCertificate,tvCertificate, btnCertificate,tvCertificate,
tvRealm,etRealm, tvRealm,etRealm,

@ -135,6 +135,29 @@
app:layout_constraintStart_toStartOf="parent" app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/cbInsecure" /> app:layout_constraintTop_toBottomOf="@id/cbInsecure" />
<CheckBox
android:id="@+id/cbDane"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="12dp"
android:layout_marginTop="12dp"
android:text="@string/title_dane"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/tvInsecureRemark" />
<TextView
android:id="@+id/tvDaneRemark"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="12dp"
android:layout_marginTop="6dp"
android:text="@string/title_dane_remark"
android:textAppearance="@style/TextAppearance.AppCompat.Small"
android:textStyle="italic"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/cbDane" />
<!-- port --> <!-- port -->
<TextView <TextView
@ -145,7 +168,7 @@
android:text="@string/title_port" android:text="@string/title_port"
android:textAppearance="@style/TextAppearance.AppCompat.Small" android:textAppearance="@style/TextAppearance.AppCompat.Small"
app:layout_constraintStart_toStartOf="parent" app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/tvInsecureRemark" /> app:layout_constraintTop_toBottomOf="@id/tvDaneRemark" />
<eu.faircode.email.EditTextPlain <eu.faircode.email.EditTextPlain
android:id="@+id/etPort" android:id="@+id/etPort"

@ -1192,6 +1192,8 @@
<string name="title_encryption_none">None (insecure)</string> <string name="title_encryption_none">None (insecure)</string>
<string name="title_allow_insecure">Allow insecure connections</string> <string name="title_allow_insecure">Allow insecure connections</string>
<string name="title_insecure_remark">Insecure connections should only be allowed on trusted networks and never on public networks</string> <string name="title_insecure_remark">Insecure connections should only be allowed on trusted networks and never on public networks</string>
<string name="title_dane">Enforce DANE</string>
<string name="title_dane_remark">Only some mail servers support DNS-based Authentication of Named Entities</string>
<string name="title_notify_remark">You can enable this for account specific notifications</string> <string name="title_notify_remark">You can enable this for account specific notifications</string>
<string name="title_port">Port number</string> <string name="title_port">Port number</string>
<string name="title_user">User name</string> <string name="title_user">User name</string>

Loading…
Cancel
Save