Check fingerprint server certificates

pull/169/head
M66B 5 years ago
parent 91d6eeada1
commit 500e1b0eef

File diff suppressed because it is too large Load Diff

@ -1223,7 +1223,7 @@ public class ActivitySetup extends ActivityBase implements FragmentManager.OnBac
String user = email;
String password = token;
try (MailService iservice = new MailService(context, "imaps", null, false, true, true)) {
iservice.connect(host, port, MailService.AUTH_TYPE_OUTLOOK, user, password);
iservice.connect(host, port, MailService.AUTH_TYPE_OUTLOOK, user, password, null);
folders = iservice.getFolders();

@ -56,7 +56,7 @@ import io.requery.android.database.sqlite.RequerySQLiteOpenHelperFactory;
// https://developer.android.com/topic/libraries/architecture/room.html
@Database(
version = 122,
version = 123,
entities = {
EntityIdentity.class,
EntityAccount.class,
@ -1188,6 +1188,14 @@ public abstract class DB extends RoomDatabase {
file.delete();
}
})
.addMigrations(new Migration(122, 123) {
@Override
public void migrate(@NonNull SupportSQLiteDatabase db) {
Log.i("DB migration from version " + startVersion + " to " + endVersion);
db.execSQL("ALTER TABLE `account` ADD COLUMN `fingerprint` TEXT");
db.execSQL("ALTER TABLE `identity` ADD COLUMN `fingerprint` TEXT");
}
})
.build();
}

@ -77,6 +77,7 @@ public class EntityAccount extends EntityOrder implements Serializable {
@NonNull
public String password;
public String realm;
public String fingerprint;
public String name;
public String signature; // obsolete

@ -77,6 +77,7 @@ public class EntityIdentity {
@NonNull
public String password;
public String realm;
public String fingerprint;
@NonNull
public Boolean use_ip = true; // instead of domain name
@NonNull

@ -127,6 +127,7 @@ public class FragmentAccount extends FragmentBase {
private ContentLoadingProgressBar pbSave;
private CheckBox cbIdentity;
private TextView tvError;
private CheckBox cbTrust;
private Button btnHelp;
private Button btnSupport;
private TextView tvInstructions;
@ -229,6 +230,7 @@ public class FragmentAccount extends FragmentBase {
cbIdentity = view.findViewById(R.id.cbIdentity);
tvError = view.findViewById(R.id.tvError);
cbTrust = view.findViewById(R.id.cbTrust);
btnHelp = view.findViewById(R.id.btnHelp);
btnSupport = view.findViewById(R.id.btnSupport);
tvInstructions = view.findViewById(R.id.tvInstructions);
@ -271,6 +273,7 @@ public class FragmentAccount extends FragmentBase {
etUser.setText(null);
tilPassword.getEditText().setText(null);
etRealm.setText(null);
cbTrust.setChecked(false);
etName.setText(position > 1 ? provider.name : null);
etInterval.setText(provider.keepalive > 0 ? Integer.toString(provider.keepalive) : null);
@ -432,6 +435,7 @@ public class FragmentAccount extends FragmentBase {
pbSave.setVisibility(View.GONE);
cbIdentity.setVisibility(View.GONE);
cbTrust.setVisibility(View.GONE);
btnHelp.setVisibility(View.GONE);
btnSupport.setVisibility(View.GONE);
tvInstructions.setVisibility(View.GONE);
@ -499,6 +503,7 @@ public class FragmentAccount extends FragmentBase {
args.putString("user", etUser.getText().toString());
args.putString("password", tilPassword.getEditText().getText().toString());
args.putString("realm", etRealm.getText().toString());
args.putString("fingerprint", cbTrust.isChecked() ? (String) cbTrust.getTag() : null);
new SimpleTask<CheckResult>() {
@Override
@ -535,6 +540,7 @@ public class FragmentAccount extends FragmentBase {
String user = args.getString("user");
String password = args.getString("password");
String realm = args.getString("realm");
String fingerprint = args.getString("fingerprint");
if (host.contains(":")) {
Uri h = Uri.parse(host);
@ -562,7 +568,7 @@ public class FragmentAccount extends FragmentBase {
// Check IMAP server / get folders
String protocol = "imap" + (starttls ? "" : "s");
try (MailService iservice = new MailService(context, protocol, realm, insecure, true, true)) {
iservice.connect(host, Integer.parseInt(port), auth, user, password);
iservice.connect(host, Integer.parseInt(port), auth, user, password, fingerprint);
result.idle = iservice.hasCapability("IDLE");
@ -639,6 +645,9 @@ public class FragmentAccount extends FragmentBase {
setFolders(result.folders, result.account);
if (!cbTrust.isChecked())
cbTrust.setVisibility(View.GONE);
new Handler().post(new Runnable() {
@Override
public void run() {
@ -701,6 +710,7 @@ public class FragmentAccount extends FragmentBase {
args.putString("user", etUser.getText().toString());
args.putString("password", tilPassword.getEditText().getText().toString());
args.putString("realm", etRealm.getText().toString());
args.putString("fingerprint", cbTrust.isChecked() ? (String) cbTrust.getTag() : null);
args.putString("name", etName.getText().toString());
args.putInt("color", btnColor.getColor());
@ -760,6 +770,7 @@ public class FragmentAccount extends FragmentBase {
String user = args.getString("user").trim();
String password = args.getString("password");
String realm = args.getString("realm");
String fingerprint = args.getString("fingerprint");
String name = args.getString("name");
Integer color = args.getInt("color");
@ -846,6 +857,8 @@ public class FragmentAccount extends FragmentBase {
return true;
if (!Objects.equals(account.realm, realm))
return true;
if (!Objects.equals(account.fingerprint, fingerprint))
return true;
if (!Objects.equals(account.name, name))
return true;
if (!Objects.equals(account.color, color))
@ -916,7 +929,8 @@ public class FragmentAccount extends FragmentBase {
!account.port.equals(Integer.parseInt(port)) ||
!account.user.equals(user) ||
!account.password.equals(password) ||
!Objects.equals(realm, accountRealm)));
!Objects.equals(realm, accountRealm) ||
!Objects.equals(account.fingerprint, fingerprint)));
Log.i("Account check=" + check);
Long last_connected = null;
@ -928,7 +942,7 @@ public class FragmentAccount extends FragmentBase {
if (check) {
String protocol = "imap" + (starttls ? "" : "s");
try (MailService iservice = new MailService(context, protocol, realm, insecure, true, true)) {
iservice.connect(host, Integer.parseInt(port), auth, user, password);
iservice.connect(host, Integer.parseInt(port), auth, user, password, fingerprint);
for (Folder ifolder : iservice.getStore().getDefaultFolder().list("*")) {
// Check folder attributes
@ -976,6 +990,7 @@ public class FragmentAccount extends FragmentBase {
account.password = password;
}
account.realm = realm;
account.fingerprint = fingerprint;
account.name = name;
account.color = color;
@ -1160,6 +1175,13 @@ public class FragmentAccount extends FragmentBase {
tvError.setText(Log.formatThrowable(ex, false));
grpError.setVisibility(View.VISIBLE);
if (ex instanceof MailService.UntrustedException) {
MailService.UntrustedException e = (MailService.UntrustedException) ex;
cbTrust.setTag(e.getFingerprint());
cbTrust.setText(getString(R.string.title_trust, e.getFingerprint()));
cbTrust.setVisibility(View.VISIBLE);
}
final EmailProvider provider = (EmailProvider) spProvider.getSelectedItem();
if (provider != null && provider.link != null) {
Uri uri = Uri.parse(provider.link);
@ -1251,6 +1273,15 @@ public class FragmentAccount extends FragmentBase {
tilPassword.getEditText().setText(account == null ? null : account.password);
etRealm.setText(account == null ? null : account.realm);
if (account == null || account.fingerprint == null) {
cbTrust.setTag(null);
cbTrust.setChecked(false);
} else {
cbTrust.setTag(account.fingerprint);
cbTrust.setChecked(true);
cbTrust.setText(getString(R.string.title_trust, account.fingerprint));
}
etName.setText(account == null ? null : account.name);
btnColor.setColor(account == null ? null : account.color);
@ -1556,10 +1587,14 @@ public class FragmentAccount extends FragmentBase {
}
}
cbIdentity.setChecked(account == null);
grpFolders.setVisibility(View.VISIBLE);
btnSave.setVisibility(View.VISIBLE);
cbIdentity.setVisibility(View.VISIBLE);
cbIdentity.setChecked(account == null);
if (cbTrust.isChecked())
cbTrust.setVisibility(View.VISIBLE);
}
private class CheckResult {

@ -285,7 +285,7 @@ public class FragmentGmail extends FragmentBase {
String aprotocol = provider.imap.starttls ? "imap" : "imaps";
try (MailService iservice = new MailService(context, aprotocol, null, false, true, true)) {
iservice.connect(provider.imap.host, provider.imap.port, MailService.AUTH_TYPE_GMAIL, user, password);
iservice.connect(provider.imap.host, provider.imap.port, MailService.AUTH_TYPE_GMAIL, user, password, null);
folders = iservice.getFolders();
@ -295,7 +295,7 @@ public class FragmentGmail extends FragmentBase {
String iprotocol = provider.smtp.starttls ? "smtp" : "smtps";
try (MailService iservice = new MailService(context, iprotocol, null, false, true, true)) {
iservice.connect(provider.smtp.host, provider.smtp.port, MailService.AUTH_TYPE_GMAIL, user, password);
iservice.connect(provider.smtp.host, provider.smtp.port, MailService.AUTH_TYPE_GMAIL, user, password, null);
}
DB db = DB.getInstance(context);

@ -110,6 +110,7 @@ public class FragmentIdentity extends FragmentBase {
private Button btnSave;
private ContentLoadingProgressBar pbSave;
private TextView tvError;
private CheckBox cbTrust;
private Button btnHelp;
private Button btnSupport;
private TextView tvInstructions;
@ -191,6 +192,7 @@ public class FragmentIdentity extends FragmentBase {
btnSave = view.findViewById(R.id.btnSave);
pbSave = view.findViewById(R.id.pbSave);
tvError = view.findViewById(R.id.tvError);
cbTrust = view.findViewById(R.id.cbTrust);
btnHelp = view.findViewById(R.id.btnHelp);
btnSupport = view.findViewById(R.id.btnSupport);
tvInstructions = view.findViewById(R.id.tvInstructions);
@ -255,6 +257,7 @@ public class FragmentIdentity extends FragmentBase {
etUser.setText(account.user);
tilPassword.getEditText().setText(account.password);
etRealm.setText(account.realm);
cbTrust.setChecked(false);
}
@Override
@ -426,6 +429,7 @@ public class FragmentIdentity extends FragmentBase {
btnSave.setVisibility(View.GONE);
pbSave.setVisibility(View.GONE);
cbTrust.setVisibility(View.GONE);
btnHelp.setVisibility(View.GONE);
btnSupport.setVisibility(View.GONE);
tvInstructions.setVisibility(View.GONE);
@ -514,6 +518,7 @@ public class FragmentIdentity extends FragmentBase {
args.putString("user", etUser.getText().toString());
args.putString("password", tilPassword.getEditText().getText().toString());
args.putString("realm", etRealm.getText().toString());
args.putString("fingerprint", cbTrust.isChecked() ? (String) cbTrust.getTag() : null);
args.putBoolean("use_ip", cbUseIp.isChecked());
args.putString("signature", (String) etSignature.getTag());
args.putBoolean("synchronize", cbSynchronize.isChecked());
@ -561,6 +566,7 @@ public class FragmentIdentity extends FragmentBase {
String user = args.getString("user").trim();
String password = args.getString("password");
String realm = args.getString("realm");
String fingerprint = args.getString("fingerprint");
boolean use_ip = args.getBoolean("use_ip");
boolean synchronize = args.getBoolean("synchronize");
boolean primary = args.getBoolean("primary");
@ -687,6 +693,8 @@ public class FragmentIdentity extends FragmentBase {
return true;
if (!Objects.equals(identity.realm, realm))
return true;
if (!Objects.equals(identity.fingerprint, fingerprint))
return true;
if (!Objects.equals(identity.use_ip, use_ip))
return true;
if (!Objects.equals(identity.synchronize, synchronize))
@ -715,6 +723,7 @@ public class FragmentIdentity extends FragmentBase {
!host.equals(identity.host) || Integer.parseInt(port) != identity.port ||
!user.equals(identity.user) || !password.equals(identity.password) ||
!Objects.equals(realm, identityRealm) ||
!Objects.equals(identity.fingerprint, fingerprint) ||
use_ip != identity.use_ip));
Log.i("Identity check=" + check);
@ -728,7 +737,7 @@ public class FragmentIdentity extends FragmentBase {
String protocol = (starttls ? "smtp" : "smtps");
try (MailService iservice = new MailService(context, protocol, realm, insecure, true, true)) {
iservice.setUseIp(use_ip);
iservice.connect(host, Integer.parseInt(port), auth, user, password);
iservice.connect(host, Integer.parseInt(port), auth, user, password, fingerprint);
}
}
@ -763,6 +772,7 @@ public class FragmentIdentity extends FragmentBase {
identity.password = password;
}
identity.realm = realm;
identity.fingerprint = fingerprint;
identity.use_ip = use_ip;
identity.synchronize = synchronize;
identity.primary = (identity.synchronize && primary);
@ -828,6 +838,13 @@ public class FragmentIdentity extends FragmentBase {
tvError.setText(Log.formatThrowable(ex, false));
grpError.setVisibility(View.VISIBLE);
if (ex instanceof MailService.UntrustedException) {
MailService.UntrustedException e = (MailService.UntrustedException) ex;
cbTrust.setTag(e.getFingerprint());
cbTrust.setText(getString(R.string.title_trust, e.getFingerprint()));
cbTrust.setVisibility(View.VISIBLE);
}
final EmailProvider provider = (EmailProvider) spProvider.getSelectedItem();
if (provider != null && provider.link != null) {
Uri uri = Uri.parse(provider.link);
@ -898,6 +915,18 @@ public class FragmentIdentity extends FragmentBase {
etUser.setText(identity == null ? null : identity.user);
tilPassword.getEditText().setText(identity == null ? null : identity.password);
etRealm.setText(identity == null ? null : identity.realm);
if (identity == null || identity.fingerprint == null) {
cbTrust.setTag(null);
cbTrust.setChecked(false);
cbTrust.setVisibility(View.GONE);
} else {
cbTrust.setTag(identity.fingerprint);
cbTrust.setChecked(true);
cbTrust.setText(getString(R.string.title_trust, identity.fingerprint));
cbTrust.setVisibility(View.VISIBLE);
}
cbUseIp.setChecked(identity == null ? true : identity.use_ip);
cbSynchronize.setChecked(identity == null ? true : identity.synchronize);
cbPrimary.setChecked(identity == null ? true : identity.primary);

@ -278,7 +278,7 @@ public class FragmentPop extends FragmentBase {
if (check) {
String protocol = "pop3" + (starttls ? "" : "s");
try (MailService iservice = new MailService(context, protocol, null, insecure, true, true)) {
iservice.connect(host, Integer.parseInt(port), MailService.AUTH_TYPE_PASSWORD, user, password);
iservice.connect(host, Integer.parseInt(port), MailService.AUTH_TYPE_PASSWORD, user, password, null);
}
}

@ -250,13 +250,13 @@ public class FragmentQuickSetup extends FragmentBase {
String aprotocol = provider.imap.starttls ? "imap" : "imaps";
try (MailService iservice = new MailService(context, aprotocol, null, false, true, true)) {
try {
iservice.connect(provider.imap.host, provider.imap.port, MailService.AUTH_TYPE_PASSWORD, user, password);
iservice.connect(provider.imap.host, provider.imap.port, MailService.AUTH_TYPE_PASSWORD, user, password, null);
} catch (AuthenticationFailedException ex) {
if (!user.equals(username)) {
Log.w(ex);
user = username;
Log.i("Retry with user=" + user);
iservice.connect(provider.imap.host, provider.imap.port, MailService.AUTH_TYPE_PASSWORD, user, password);
iservice.connect(provider.imap.host, provider.imap.port, MailService.AUTH_TYPE_PASSWORD, user, password, null);
} else
throw ex;
}
@ -270,7 +270,7 @@ public class FragmentQuickSetup extends FragmentBase {
String iprotocol = provider.smtp.starttls ? "smtp" : "smtps";
try (MailService iservice = new MailService(context, iprotocol, null, false, true, true)) {
iservice.setUseIp(provider.useip);
iservice.connect(provider.smtp.host, provider.smtp.port, MailService.AUTH_TYPE_PASSWORD, user, password);
iservice.connect(provider.smtp.host, provider.smtp.port, MailService.AUTH_TYPE_PASSWORD, user, password, null);
}
if (check)

@ -6,6 +6,7 @@ import android.content.Context;
import android.content.SharedPreferences;
import android.text.TextUtils;
import androidx.annotation.NonNull;
import androidx.preference.PreferenceManager;
import com.sun.mail.imap.IMAPFolder;
@ -15,7 +16,6 @@ import com.sun.mail.util.MailConnectException;
import com.sun.mail.util.MailSSLSocketFactory;
import org.bouncycastle.asn1.x509.GeneralName;
import org.jetbrains.annotations.NotNull;
import java.io.IOException;
import java.net.Inet4Address;
@ -121,6 +121,7 @@ public class MailService implements AutoCloseable {
}
}
};
properties.put("mail." + protocol + ".ssl.socketFactory", sf);
properties.put("mail." + protocol + ".socketFactory.fallback", "false");
} catch (GeneralSecurityException ex) {
@ -160,8 +161,7 @@ public class MailService implements AutoCloseable {
// https://javaee.github.io/javamail/docs/api/com/sun/mail/pop3/package-summary.html#properties
properties.put("mail." + protocol + ".ssl.checkserveridentity", Boolean.toString(!insecure));
if (!BuildConfig.DEBUG)
properties.put("mail." + protocol + ".ssl.trust", "*");
properties.put("mail." + protocol + ".ssl.trust", "*");
properties.put("mail.pop3s.starttls.enable", "false");
@ -170,10 +170,6 @@ public class MailService implements AutoCloseable {
} else if ("imap".equals(protocol) || "imaps".equals(protocol)) {
// https://javaee.github.io/javamail/docs/api/com/sun/mail/imap/package-summary.html#properties
properties.put("mail." + protocol + ".ssl.checkserveridentity", Boolean.toString(!insecure));
if (!BuildConfig.DEBUG)
properties.put("mail." + protocol + ".ssl.trust", "*");
properties.put("mail.imaps.starttls.enable", "false");
properties.put("mail.imap.starttls.enable", "true");
@ -200,10 +196,6 @@ public class MailService implements AutoCloseable {
} else if ("smtp".equals(protocol) || "smtps".equals(protocol)) {
// https://javaee.github.io/javamail/docs/api/com/sun/mail/smtp/package-summary.html#properties
properties.put("mail." + protocol + ".ssl.checkserveridentity", Boolean.toString(!insecure));
if (!BuildConfig.DEBUG)
properties.put("mail." + protocol + ".ssl.trust", "*");
properties.put("mail.smtps.starttls.enable", "false");
properties.put("mail.smtp.starttls.enable", "true");
@ -235,12 +227,8 @@ public class MailService implements AutoCloseable {
this.listener = listener;
}
void setTrustedFingerPrint(String value) {
trustedFingerprint = value;
}
public void connect(EntityAccount account) throws MessagingException {
String password = connect(account.host, account.port, account.auth_type, account.user, account.password);
String password = connect(account.host, account.port, account.auth_type, account.user, account.password, account.fingerprint);
if (password != null) {
DB db = DB.getInstance(context);
int count = db.account().setAccountPassword(account.id, account.password);
@ -249,7 +237,7 @@ public class MailService implements AutoCloseable {
}
public void connect(EntityIdentity identity) throws MessagingException {
String password = connect(identity.host, identity.port, identity.auth_type, identity.user, identity.password);
String password = connect(identity.host, identity.port, identity.auth_type, identity.user, identity.password, identity.fingerprint);
if (password != null) {
DB db = DB.getInstance(context);
int count = db.identity().setIdentityPassword(identity.id, identity.password);
@ -257,7 +245,8 @@ public class MailService implements AutoCloseable {
}
}
public String connect(String host, int port, int auth, String user, String password) throws MessagingException {
public String connect(String host, int port, int auth, String user, String password, String fingerprint) throws MessagingException {
this.trustedFingerprint = fingerprint;
try {
if (auth == AUTH_TYPE_GMAIL || auth == AUTH_TYPE_OUTLOOK)
properties.put("mail." + protocol + ".auth.mechanisms", "XOAUTH2");
@ -317,16 +306,14 @@ public class MailService implements AutoCloseable {
ex.getCause() instanceof IOException &&
ex.getCause().getMessage() != null &&
ex.getCause().getMessage().startsWith("Server is not trusted:")) {
String name;
String fingerprint;
String sfingerprint;
try {
name = getDnsName(certificate);
fingerprint = getFingerPrint(certificate);
sfingerprint = getFingerPrint(certificate);
} catch (Throwable ex1) {
Log.e(ex1);
throw ex;
}
throw new UntrustedException(name, fingerprint);
throw new UntrustedException(sfingerprint, ex);
} else
throw ex;
}
@ -516,26 +503,20 @@ public class MailService implements AutoCloseable {
}
class UntrustedException extends MessagingException {
private String name;
private String fingerprint;
UntrustedException(String name, String fingerprint) {
this.name = name;
UntrustedException(@NonNull String fingerprint, @NonNull Exception cause) {
super(cause.getMessage(), cause);
this.fingerprint = fingerprint;
}
String getName() {
return name;
}
String getFingerprint() {
return fingerprint;
}
@NotNull
@Override
public synchronized String toString() {
return this.getClass().getName() + " name=" + name + " fingerprint=" + fingerprint;
return getCause().toString();
}
}
}

@ -37,6 +37,7 @@ public class TupleAccountState extends EntityAccount {
this.user.equals(other.user) &&
this.password.equals(other.password) &&
Objects.equals(this.realm, other.realm) &&
Objects.equals(this.fingerprint, other.fingerprint) &&
this.notify.equals(other.notify) &&
this.poll_interval.equals(other.poll_interval) &&
this.partial_fetch.equals(other.partial_fetch) &&

@ -814,6 +814,17 @@
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/tvErrorTitle" />
<CheckBox
android:id="@+id/cbTrust"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="12dp"
android:text="@string/title_trust"
android:textColor="?attr/colorWarning"
android:textStyle="bold"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/tvError" />
<Button
android:id="@+id/btnHelp"
android:layout_width="wrap_content"
@ -824,7 +835,7 @@
android:textColor="@android:color/black"
android:textStyle="bold"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/tvError" />
app:layout_constraintTop_toBottomOf="@id/cbTrust" />
<Button
android:id="@+id/btnSupport"
@ -836,7 +847,7 @@
android:textColor="@android:color/black"
android:textStyle="bold"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toBottomOf="@id/tvError" />
app:layout_constraintTop_toBottomOf="@id/cbTrust" />
<TextView
android:id="@+id/tvInstructions"

@ -617,6 +617,17 @@
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/tvErrorTitle" />
<CheckBox
android:id="@+id/cbTrust"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="12dp"
android:text="@string/title_trust"
android:textColor="?attr/colorWarning"
android:textStyle="bold"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/tvError" />
<Button
android:id="@+id/btnHelp"
android:layout_width="wrap_content"
@ -627,7 +638,7 @@
android:textColor="@android:color/black"
android:textStyle="bold"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/tvError" />
app:layout_constraintTop_toBottomOf="@id/cbTrust" />
<Button
android:id="@+id/btnSupport"
@ -639,7 +650,7 @@
android:textColor="@android:color/black"
android:textStyle="bold"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toBottomOf="@id/tvError" />
app:layout_constraintTop_toBottomOf="@id/cbTrust" />
<TextView
android:id="@+id/tvInstructions"

@ -469,6 +469,7 @@
<string name="title_use_date">Use date header sent time instead of server received time</string>
<string name="title_related_identity">Add related identity</string>
<string name="title_check">Check</string>
<string name="title_trust">Trust %1$s</string>
<string name="title_no_name">Name missing</string>
<string name="title_no_email">Email address missing</string>
<string name="title_email_invalid">Email address invalid: \'%1$s\'</string>

Loading…
Cancel
Save