integrate trust manager into conversations

This commit is contained in:
Christian Schneppe 2018-03-12 20:58:37 +01:00
parent 89927cee8e
commit f5854d238e
34 changed files with 163 additions and 619 deletions

View file

@ -30,7 +30,7 @@ allprojects {
}
configurations {
standardPushCompile
standardPushimplementation
}
ext {
@ -38,36 +38,35 @@ ext {
}
dependencies {
compile project(':libs:MemorizingTrustManager')
compile project(':libs:android-transcoder')
standardPushCompile 'com.google.android.gms:play-services-gcm:11.8.0'
compile 'org.sufficientlysecure:openpgp-api:10.0'
compile 'com.soundcloud.android:android-crop:1.0.1@aar'
compile 'org.bouncycastle:bcmail-jdk15on:1.58'
compile 'org.jitsi:org.otr4j:0.22'
compile 'org.gnu.inet:libidn:1.15'
compile 'com.google.zxing:core:3.3.0'
compile 'de.measite.minidns:minidns-hla:0.2.4'
compile 'me.leolin:ShortcutBadger:1.1.21@aar'
compile 'com.kyleduo.switchbutton:library:1.2.8'
compile 'org.whispersystems:signal-protocol-java:2.6.2'
compile 'com.makeramen:roundedimageview:2.3.0'
compile 'jetty:javax.servlet:5.1.12'
compile 'com.google.code.gson:gson:2.8.0'
compile 'com.android.support:multidex:1.0.3'
compile "com.android.support:support-v13:$supportLibVersion"
compile "com.android.support:appcompat-v7:$supportLibVersion"
compile "com.android.support:support-v4:$supportLibVersion"
compile "com.android.support:support-emoji:$supportLibVersion"
compile "com.android.support:support-emoji-bundled:$supportLibVersion"
compile "com.android.support:support-emoji-appcompat:$supportLibVersion"
compile "com.android.support:exifinterface:$supportLibVersion"
compile 'com.github.bumptech.glide:glide:3.8.0'
compile 'com.github.chrisbanes:PhotoView:2.0.0'
compile 'com.github.rtoshiro.fullscreenvideoview:fullscreenvideoview:1.1.3'
compile 'pub.devrel:easypermissions:1.1.3'
compile 'com.wefika:flowlayout:0.4.1'
compile 'com.googlecode.ez-vcard:ez-vcard:0.10.3'
implementation project(':libs:android-transcoder')
standardPushimplementation 'com.google.android.gms:play-services-gcm:11.8.0'
implementation 'org.sufficientlysecure:openpgp-api:10.0'
implementation 'com.soundcloud.android:android-crop:1.0.1@aar'
implementation 'org.bouncycastle:bcmail-jdk15on:1.58'
implementation 'org.jitsi:org.otr4j:0.22'
implementation 'org.gnu.inet:libidn:1.15'
implementation 'com.google.zxing:core:3.3.0'
implementation 'de.measite.minidns:minidns-hla:0.2.4'
implementation 'me.leolin:ShortcutBadger:1.1.21@aar'
implementation 'com.kyleduo.switchbutton:library:1.2.8'
implementation 'org.whispersystems:signal-protocol-java:2.6.2'
implementation 'com.makeramen:roundedimageview:2.3.0'
implementation 'jetty:javax.servlet:5.1.12'
implementation 'com.google.code.gson:gson:2.8.0'
implementation 'com.android.support:multidex:1.0.3'
implementation "com.android.support:support-v13:$supportLibVersion"
implementation "com.android.support:appcompat-v7:$supportLibVersion"
implementation "com.android.support:support-v4:$supportLibVersion"
implementation "com.android.support:support-emoji:$supportLibVersion"
implementation "com.android.support:support-emoji-bundled:$supportLibVersion"
implementation "com.android.support:support-emoji-appcompat:$supportLibVersion"
implementation "com.android.support:exifinterface:$supportLibVersion"
implementation 'com.github.bumptech.glide:glide:3.8.0'
implementation 'com.github.chrisbanes:PhotoView:2.0.0'
implementation 'com.github.rtoshiro.fullscreenvideoview:fullscreenvideoview:1.1.3'
implementation 'pub.devrel:easypermissions:1.1.3'
implementation 'com.wefika:flowlayout:0.4.1'
implementation 'com.googlecode.ez-vcard:ez-vcard:0.10.3'
}
ext {

View file

@ -1,11 +0,0 @@
bin
build
gen
local.properties
example/bin
example/gen
tags
.project
.classpath
.gradle
.*.swp

View file

@ -1,12 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="de.duenndns.ssl"
android:versionCode="1"
android:versionName="1.0">
<application android:label="MemorizingTrustManager">
<activity
android:name="de.duenndns.ssl.MemorizingActivity"
android:theme="@android:style/Theme.Translucent.NoTitleBar" />
</application>
</manifest>

View file

@ -1,21 +0,0 @@
The MIT license.
Copyright (c) 2010 Georg Lukas <georg@op-co.de>
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.

View file

@ -1,125 +0,0 @@
# MemorizingTrustManager - Private Cloud Support for Your App
MemorizingTrustManager (MTM) is a project to enable smarter and more secure use
of SSL on Android. If it encounters an unknown SSL certificate, it asks the
user whether to accept the certificate once, permanently or to abort the
connection. This is a step in preventing man-in-the-middle attacks by blindly
accepting any invalid, self-signed and/or expired certificates.
MTM is aimed at providing seamless integration into your Android application,
and the source code is available under the MIT license.
## Screenshots
![MemorizingTrustManager dialog](mtm-screenshot.png)
![MemorizingTrustManager notification](mtm-notification.png)
![MemorizingTrustManager server name dialog](mtm-servername.png)
## Status
MemorizingTrustManager is in production use in the
[yaxim XMPP client](https://yaxim.org/). It is usable and easy to integrate,
though it does not yet support hostname validation (the Java API makes it
**hard** to integrate).
## Integration
MTM is easy to integrate into your own application. Follow these steps or have
a look into the demo application in the `example` directory.
### 1. Add MTM to your project
Download the MTM source from GitHub, or add it as a
[git submodule](http://git-scm.com/docs/git-submodule):
# plain download:
git clone https://github.com/ge0rg/MemorizingTrustManager
# submodule:
git submodule add https://github.com/ge0rg/MemorizingTrustManager
Then add a library project dependency to `default.properties`:
android.library.reference.1=MemorizingTrustManager
### 2. Add the MTM (popup) Activity to your manifest
Edit your `AndroidManifest.xml` and add the MTM activity element right before the
end of your closing `</application>` tag.
...
<activity android:name="de.duenndns.ssl.MemorizingActivity"
android:theme="@android:style/Theme.Translucent.NoTitleBar"
/>
</application>
</manifest>
### 3. Hook MTM as the default TrustManager for your connection type
Hooking MemorizingTrustmanager in HTTPS connections:
// register MemorizingTrustManager for HTTPS
SSLContext sc = SSLContext.getInstance("TLS");
MemorizingTrustManager mtm = new MemorizingTrustManager(this);
sc.init(null, new X509TrustManager[] { mtm }, new java.security.SecureRandom());
HttpsURLConnection.setDefaultSSLSocketFactory(sc.getSocketFactory());
HttpsURLConnection.setDefaultHostnameVerifier(
mtm.wrapHostnameVerifier(HttpsURLConnection.getDefaultHostnameVerifier()));
Or, for aSmack you can use `setCustomSSLContext()`:
org.jivesoftware.smack.ConnectionConfiguration connectionConfiguration = …
SSLContext sc = SSLContext.getInstance("TLS");
MemorizingTrustManager mtm = new MemorizingTrustManager(this);
sc.init(null, new X509TrustManager[] { mtm }, new java.security.SecureRandom());
connectionConfiguration.setCustomSSLContext(sc);
connectionConfiguration.setHostnameVerifier(
mtm.wrapHostnameVerifier(new org.apache.http.conn.ssl.StrictHostnameVerifier()));
By default, MTM falls back to the system `TrustManager` before asking the user.
If you do not trust the establishment, you can enforce a dialog on *every new
connection* by supplying a `defaultTrustManager = null` parameter to the
constructor:
MemorizingTrustManager mtm = new MemorizingTrustManager(this, null);
If you want to use a different underlying `TrustManager`, like
[AndroidPinning](https://github.com/moxie0/AndroidPinning), just supply that to
MTM's constructor:
X509TrustManager pinning = new PinningTrustManager(SystemKeyStore.getInstance(),
new String[] {"f30012bbc18c231ac1a44b788e410ce754182513"}, 0);
MemorizingTrustManager mtm = new MemorizingTrustManager(this, pinning);
### 4. Profit!
### Logging
MTM uses java.util.logging (JUL) for logging purposes. If you have not
configured a Handler for JUL, then Android will by default log all
messages of Level.INFO or higher. In order to get also the debug log
messages (those with Level.FINE or lower) you need to configure a
Handler accordingly. The MTM example project contains
de.duenndns.mtmexample.JULHandler, which allows to enable and disable
debug logging at runtime.
## Alternatives
MemorizingTrustManager is not the only one out there.
[**NetCipher**](https://guardianproject.info/code/netcipher/) is an Android
library made by the [Guardian Project](https://guardianproject.info/) to
improve network security for mobile apps. It comes with a StrongTrustManager
to do more thorough certificate checks, an independent Root CA store, and code
to easily route your traffic through
[the Tor network](https://www.torproject.org/) using [Orbot](https://guardianproject.info/apps/orbot/).
[**AndroidPinning**](https://github.com/moxie0/AndroidPinning) is another Android
library, written by [Moxie Marlinspike](http://www.thoughtcrime.org/) to allow
pinning of server certificates, improving security against government-scale
MitM attacks. Use this if your app is made to communicate with a specific
server!
## Contribute
Please [help translating MTM into more languages](https://translations.launchpad.net/yaxim/master/+pots/mtm/)!

View file

@ -1,14 +0,0 @@
# This file is used to override default values used by the Ant build system.
#
# This file must be checked in Version Control Systems, as it is
# integral to the build system of your project.
# This file is only used by the Ant script.
# You can use this to override default values such as
# 'source.dir' for the location of your java source folder and
# 'out.dir' for the location of your output folder.
# You can also use it define how the release builds are signed by declaring
# the following properties:
# 'key.store' for the location of your keystore and
# 'key.alias' for the name of the key to use.
# The password will be asked during the build when you use the 'release' target.

View file

@ -1,33 +0,0 @@
buildscript {
repositories {
mavenCentral()
jcenter()
}
dependencies {
classpath 'com.android.tools.build:gradle:3.0.1'
}
}
apply plugin: 'com.android.library'
android {
compileSdkVersion 27
buildToolsVersion "27.0.3"
defaultConfig {
minSdkVersion 14
targetSdkVersion 26
}
sourceSets {
main {
manifest.srcFile 'AndroidManifest.xml'
java.srcDirs = ['src']
resources.srcDirs = ['src']
aidl.srcDirs = ['src']
renderscript.srcDirs = ['src']
res.srcDirs = ['res']
assets.srcDirs = ['assets']
}
}
}

View file

@ -1,91 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<project name="MemorizingTrustManager" default="help">
<!-- The local.properties file is created and updated by the 'android' tool.
It contains the path to the SDK. It should *NOT* be checked into
Version Control Systems. -->
<property file="local.properties" />
<!-- The ant.properties file can be created by you. It is only edited by the
'android' tool to add properties to it.
This is the place to change some Ant specific build properties.
Here are some properties you may want to change/update:
source.dir
The name of the source directory. Default is 'src'.
out.dir
The name of the output directory. Default is 'bin'.
For other overridable properties, look at the beginning of the rules
files in the SDK, at tools/ant/build.xml
Properties related to the SDK location or the project target should
be updated using the 'android' tool with the 'update' action.
This file is an integral part of the build system for your
application and should be checked into Version Control Systems.
-->
<property file="ant.properties" />
<!-- if sdk.dir was not set from one of the property file, then
get it from the ANDROID_HOME env var.
This must be done before we load project.properties since
the proguard config can use sdk.dir -->
<property environment="env" />
<condition property="sdk.dir" value="${env.ANDROID_HOME}">
<isset property="env.ANDROID_HOME" />
</condition>
<!-- The project.properties file is created and updated by the 'android'
tool, as well as ADT.
This contains project specific properties such as project target, and library
dependencies. Lower level build properties are stored in ant.properties
(or in .classpath for Eclipse projects).
This file is an integral part of the build system for your
application and should be checked into Version Control Systems. -->
<loadproperties srcFile="project.properties" />
<!-- quick check on sdk.dir -->
<fail
message="sdk.dir is missing. Make sure to generate local.properties using 'android update project' or to inject it through the ANDROID_HOME environment variable."
unless="sdk.dir" />
<!--
Import per project custom build rules if present at the root of the project.
This is the place to put custom intermediary targets such as:
-pre-build
-pre-compile
-post-compile (This is typically used for code obfuscation.
Compiled code location: ${out.classes.absolute.dir}
If this is not done in place, override ${out.dex.input.absolute.dir})
-post-package
-post-build
-pre-clean
-->
<import file="custom_rules.xml" optional="true" />
<!-- Import the actual build file.
To customize existing targets, there are two options:
- Customize only one target:
- copy/paste the target into this file, *before* the
<import> task.
- customize it to your needs.
- Customize the whole content of build.xml
- copy/paste the content of the rules files (minus the top node)
into this file, replacing the <import> task.
- customize to your needs.
***********************
****** IMPORTANT ******
***********************
In all cases you must update the value of version-tag below to read 'custom' instead of an integer,
in order to avoid having your file be overridden by tools such as "android update project"
-->
<!-- version-tag: 1 -->
<import file="${sdk.dir}/tools/ant/build.xml" />
</project>

Binary file not shown.

Before

Width:  |  Height:  |  Size: 50 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 57 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 56 KiB

View file

@ -1,20 +0,0 @@
# To enable ProGuard in your project, edit project.properties
# to define the proguard.config property as described in that file.
#
# Add project specific ProGuard rules here.
# By default, the flags in this file are appended to flags specified
# in ${sdk.dir}/tools/proguard/proguard-android.txt
# You can edit the include path and order by changing the ProGuard
# include property in project.properties.
#
# For more details, see
# http://developer.android.com/guide/developing/tools/proguard.html
# Add any project specific keep options here:
# If your project uses WebView with JS, uncomment the following
# and specify the fully qualified class name to the JavaScript interface
# class:
#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
# public *;
#}

View file

@ -1,11 +0,0 @@
# This file is automatically generated by Android Tools.
# Do not modify this file -- YOUR CHANGES WILL BE ERASED!
#
# This file must be checked in Version Control Systems.
#
# To customize properties used by the Ant build system use,
# "ant.properties", and override values to adapt the script to your
# project structure.
android.library=true
# Project target.
target=android-19

View file

@ -1,17 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="mtm_accept_cert">Unbekanntes Zertifikat akzeptieren?</string>
<string name="mtm_trust_anchor">Das Serverzertifikat stammt nicht von einer bekannten Ausstellungsstelle (CA).</string>
<string name="mtm_cert_expired">The server certificate is expired.</string>
<string name="mtm_accept_servername">Abweichenden Servernamen akzeptieren?</string>
<string name="mtm_hostname_mismatch">Der Server konnte sich nicht als \"%s\" ausweisen. Das Zertifikat gilt nur für:</string>
<string name="mtm_connect_anyway">Verbindung trotzdem aufbauen?</string>
<string name="mtm_cert_details">Zertifikat-Details:</string>
<string name="mtm_decision_always">Immer</string>
<string name="mtm_decision_once">Einmal</string>
<string name="mtm_decision_abort">Abbrechen</string>
<string name="mtm_notification">Zertifikatsprüfung</string>
</resources>

View file

@ -1,17 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="mtm_accept_cert">¿Aceptar certicado desconocido?</string>
<string name="mtm_trust_anchor">El certificado del servidor no está firmado por una Autoridad Conocida (CA).</string>
<string name="mtm_cert_expired">The server certificate is expired.</string>
<string name="mtm_accept_servername">¿Aceptar discordancia en nombre del servidor?</string>
<string name="mtm_hostname_mismatch">El servidor no ha podido autenticarte como \"%s\". El certificado es solo válido para:</string>
<string name="mtm_connect_anyway">¿Quieres conectar de todas formas?</string>
<string name="mtm_cert_details">Detalle del certificado:</string>
<string name="mtm_decision_always">Siempre</string>
<string name="mtm_decision_once">Una vez</string>
<string name="mtm_decision_abort">Abortar</string>
<string name="mtm_notification">Verificación de Certificado</string>
</resources>

View file

@ -1,17 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="mtm_accept_cert">Ziurtagiri ezezaguna onartu?</string>
<string name="mtm_trust_anchor">Zerbitzariaren ziurtagiria ez dago Ziurtagiri-emaile Autoritate ezagun batez sinatuta.</string>
<string name="mtm_cert_expired">Zerbitzariaren ziurtagiria iraungi da.</string>
<string name="mtm_accept_servername">Zerbitzariaren izeneko desadostasuna onartu?</string>
<string name="mtm_hostname_mismatch">Zerbitzaria ezin izan da \&quot;%s\&quot; bezala autentifikatu. Ziurtagiria soilik honetarako baliagarria da:</string>
<string name="mtm_connect_anyway">Konektatu hala ere?</string>
<string name="mtm_cert_details">Ziurtagiriaren xehetasunak:</string>
<string name="mtm_decision_always">Beti</string>
<string name="mtm_decision_once">Behin</string>
<string name="mtm_decision_abort">Utzi</string>
<string name="mtm_notification">Ziurtagiriaren egiaztapena</string>
</resources>

View file

@ -1,16 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="mtm_accept_cert">Hyväksytäänkö palvelimen antama tuntematon varmenne?</string>
<string name="mtm_trust_anchor">Palvelimen varmenne ei ole tunnetun varmentajan (CA) allekirjoittama.</string>
<string name="mtm_accept_servername">Sallitaanko palvelimen nimi, joka ei vastaa varmeenteessa olevaa nimeä?</string>
<string name="mtm_hostname_mismatch">Palvelimella ei ole varmennetta nimelle \"%s\". Varmenteen sisältämät nimet:</string>
<string name="mtm_connect_anyway">Haluatko jatkaa yhteyden muodostamista?</string>
<string name="mtm_cert_details">Sertifikaatin tiedot:</string>
<string name="mtm_decision_always">Aina</string>
<string name="mtm_decision_once">Kerran</string>
<string name="mtm_decision_abort">Keskeytä</string>
<string name="mtm_notification">Varmenteen tarkistus</string>
</resources>

View file

@ -1,16 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="mtm_accept_cert">Accept Unknown Certificate?</string>
<string name="mtm_trust_anchor">Le certificat du serveur nest pas signé par une Autorité de Certification reconnue.</string>
<string name="mtm_accept_servername">Accept Mismatching Server Name?</string>
<string name="mtm_hostname_mismatch">Server could not authenticate as \"%s\". The certificate is only valid for:</string>
<string name="mtm_connect_anyway">Do you want to connect anyway?</string>
<string name="mtm_cert_details">Détails du certificat:</string>
<string name="mtm_decision_always">Toujours</string>
<string name="mtm_decision_once">Une seule fois</string>
<string name="mtm_decision_abort">Annuler</string>
<string name="mtm_notification">Certificate Verification</string>
</resources>

View file

@ -1,16 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="mtm_accept_cert">Godta ukjent sertifikat?</string>
<string name="mtm_trust_anchor">Sertifikatet er ikke utstilt av en kjent utstiller (CA).</string>
<string name="mtm_accept_servername">Godta feil servernavn?</string>
<string name="mtm_hostname_mismatch">Serveren heter ikke \"%s\". Sertifikatet gjelder bare for: </string>
<string name="mtm_connect_anyway">Vil du bruke serveren likevel?</string>
<string name="mtm_cert_details">Sertifikatdetaljer:</string>
<string name="mtm_decision_always">Alltid</string>
<string name="mtm_decision_once">En gang</string>
<string name="mtm_decision_abort">Avbryt</string>
<string name="mtm_notification">Sertifikat-sjekk</string>
</resources>

View file

@ -1,4 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="theme">light</string>
</resources>

View file

@ -1,17 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="mtm_accept_cert">Accept Unknown Certificate?</string>
<string name="mtm_trust_anchor">The server certificate is not signed by a known Certificate Authority.</string>
<string name="mtm_cert_expired">The server certificate is expired.</string>
<string name="mtm_accept_servername">Accept Mismatching Server Name?</string>
<string name="mtm_hostname_mismatch">Server could not authenticate as \&quot;%s\&quot;. The certificate is only valid for:</string>
<string name="mtm_connect_anyway">Do you want to connect anyway?</string>
<string name="mtm_cert_details">Certificate details:</string>
<string name="mtm_decision_always">Always</string>
<string name="mtm_decision_once">Once</string>
<string name="mtm_decision_abort">Abort</string>
<string name="mtm_notification">Certificate Verification</string>
</resources>

View file

@ -1 +0,0 @@
include ':example'

View file

@ -1,3 +1,2 @@
include ':libs:MemorizingTrustManager'
include ':libs:android-transcoder'
rootProject.name = 'Pix-Art Messenger'

View file

@ -237,10 +237,6 @@
android:name=".ui.TrustKeysActivity"
android:label="@string/trust_omemo_fingerprints"
android:windowSoftInputMode="stateAlwaysHidden" />
<activity
android:name="de.duenndns.ssl.MemorizingActivity" />
<!--android:theme="@style/ConversationsTheme"-->
<!--tools:replace="android:theme" />-->
<activity
android:name=".ui.AboutActivity"
android:label="@string/title_activity_about"
@ -256,11 +252,8 @@
android:launchMode="singleTask"
android:theme="@style/ConversationsTheme">
</activity>
<activity
android:name="com.soundcloud.android.crop.CropImageActivity">
</activity>
<service android:name=".services.UpdateService" />
<activity android:name="com.soundcloud.android.crop.CropImageActivity" />
<activity android:name=".ui.MemorizingActivity" />
<service android:name=".services.ExportLogsService" />
<service
android:name=".services.ContactChooserTargetService"

View file

@ -1,4 +1,4 @@
package de.duenndns.ssl;
package de.pixart.messenger.crypto;
import javax.net.ssl.HostnameVerifier;
import javax.net.ssl.SSLSession;
@ -7,4 +7,4 @@ public interface DomainHostnameVerifier extends HostnameVerifier {
boolean verify(String domain, String hostname, SSLSession sslSession);
}
}

View file

@ -25,8 +25,6 @@ import java.util.List;
import javax.net.ssl.SSLSession;
import de.duenndns.ssl.DomainHostnameVerifier;
public class XmppDomainVerifier implements DomainHostnameVerifier {
private static final String LOGTAG = "XmppDomainVerifier";

View file

@ -21,13 +21,13 @@
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
package de.duenndns.ssl;
package de.pixart.messenger.entities;
class MTMDecision {
public final static int DECISION_INVALID = 0;
public final static int DECISION_ABORT = 1;
public final static int DECISION_ONCE = 2;
public final static int DECISION_ALWAYS = 3;
public class MTMDecision {
public final static int DECISION_INVALID = 0;
public final static int DECISION_ABORT = 1;
public final static int DECISION_ONCE = 2;
public final static int DECISION_ALWAYS = 3;
int state = DECISION_INVALID;
}
public int state = DECISION_INVALID;
}

View file

@ -24,7 +24,7 @@
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
package de.duenndns.ssl;
package de.pixart.messenger.services;
import android.app.Activity;
import android.app.Application;
@ -33,11 +33,11 @@ import android.app.Service;
import android.content.Context;
import android.content.Intent;
import android.net.Uri;
import android.os.Handler;
import android.preference.PreferenceManager;
import android.util.Base64;
import android.util.Log;
import android.util.SparseArray;
import android.os.Handler;
import org.json.JSONArray;
import org.json.JSONException;
@ -52,24 +52,19 @@ import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.net.URL;
import java.security.NoSuchAlgorithmException;
import java.security.cert.*;
import java.security.KeyStore;
import java.security.KeyStoreException;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.security.cert.Certificate;
import java.security.cert.CertificateEncodingException;
import java.security.cert.CertificateException;
import java.security.cert.CertificateExpiredException;
import java.security.cert.CertificateParsingException;
import java.security.cert.X509Certificate;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.logging.Level;
import java.util.logging.Logger;
import java.text.SimpleDateFormat;
import java.util.Collection;
import java.util.Enumeration;
import java.util.List;
import java.util.Locale;
import java.util.logging.Level;
import java.util.logging.Logger;
import java.util.regex.Pattern;
import javax.net.ssl.HostnameVerifier;
@ -79,6 +74,11 @@ import javax.net.ssl.TrustManager;
import javax.net.ssl.TrustManagerFactory;
import javax.net.ssl.X509TrustManager;
import de.pixart.messenger.R;
import de.pixart.messenger.crypto.DomainHostnameVerifier;
import de.pixart.messenger.entities.MTMDecision;
import de.pixart.messenger.ui.MemorizingActivity;
/**
* A X509 trust manager implementation which asks the user about invalid
* certificates and memorizes their decision.
@ -99,12 +99,12 @@ public class MemorizingTrustManager {
private static final Pattern PATTERN_IPV6 = Pattern.compile("\\A(?:[0-9a-fA-F]{1,4}:){7}[0-9a-fA-F]{1,4}\\z");
final static String DECISION_INTENT = "de.duenndns.ssl.DECISION";
final static String DECISION_INTENT_ID = DECISION_INTENT + ".decisionId";
final static String DECISION_INTENT_CERT = DECISION_INTENT + ".cert";
public final static String DECISION_INTENT_ID = DECISION_INTENT + ".decisionId";
public final static String DECISION_INTENT_CERT = DECISION_INTENT + ".cert";
final static String DECISION_INTENT_CHOICE = DECISION_INTENT + ".decisionChoice";
private final static Logger LOGGER = Logger.getLogger(MemorizingTrustManager.class.getName());
final static String DECISION_TITLE_ID = DECISION_INTENT + ".titleId";
public final static String DECISION_TITLE_ID = DECISION_INTENT + ".titleId";
private final static int NOTIFICATION_ID = 100509;
final static String NO_TRUST_ANCHOR = "Trust anchor for certification path not found.";
@ -125,18 +125,17 @@ public class MemorizingTrustManager {
private X509TrustManager appTrustManager;
private String poshCacheDir;
/**
* Creates an instance of the MemorizingTrustManager class that falls back to a custom TrustManager.
* <p>
/** Creates an instance of the MemorizingTrustManager class that falls back to a custom TrustManager.
*
* You need to supply the application context. This has to be one of:
* - Application
* - Activity
* - Service
* <p>
* - Application
* - Activity
* - Service
*
* The context is used for file management, to display the dialog /
* notification and for obtaining translated strings.
*
* @param m Context for the application.
* @param m Context for the application.
* @param defaultTrustManager Delegate trust management to this TM. If null, the user must accept every certificate.
*/
public MemorizingTrustManager(Context m, X509TrustManager defaultTrustManager) {
@ -145,14 +144,13 @@ public class MemorizingTrustManager {
this.defaultTrustManager = defaultTrustManager;
}
/**
* Creates an instance of the MemorizingTrustManager class using the system X509TrustManager.
* <p>
/** Creates an instance of the MemorizingTrustManager class using the system X509TrustManager.
*
* You need to supply the application context. This has to be one of:
* - Application
* - Activity
* - Service
* <p>
* - Application
* - Activity
* - Service
*
* The context is used for file management, to display the dialog /
* notification and for obtaining translated strings.
*
@ -167,22 +165,21 @@ public class MemorizingTrustManager {
void init(Context m) {
master = m;
masterHandler = new Handler(m.getMainLooper());
notificationManager = (NotificationManager) master.getSystemService(Context.NOTIFICATION_SERVICE);
notificationManager = (NotificationManager)master.getSystemService(Context.NOTIFICATION_SERVICE);
Application app;
if (m instanceof Application) {
app = (Application) m;
app = (Application)m;
} else if (m instanceof Service) {
app = ((Service) m).getApplication();
app = ((Service)m).getApplication();
} else if (m instanceof Activity) {
app = ((Activity) m).getApplication();
} else
throw new ClassCastException("MemorizingTrustManager context must be either Activity or Service!");
app = ((Activity)m).getApplication();
} else throw new ClassCastException("MemorizingTrustManager context must be either Activity or Service!");
File dir = app.getDir(KEYSTORE_DIR, Context.MODE_PRIVATE);
keyStoreFile = new File(dir + File.separator + KEYSTORE_FILE);
poshCacheDir = app.getFilesDir().getAbsolutePath() + "/posh_cache/";
poshCacheDir = app.getFilesDir().getAbsolutePath()+"/posh_cache/";
appKeyStore = loadAppKeyStore();
}
@ -190,11 +187,11 @@ public class MemorizingTrustManager {
/**
* Binds an Activity to the MTM for displaying the query dialog.
* <p>
*
* This is useful if your connection is run from a service that is
* triggered by user interaction -- in such cases the activity is
* visible and the user tends to ignore the service notification.
* <p>
*
* You should never have a hidden activity bound to MTM! Use this
* function in onResume() and @see unbindDisplayActivity in onPause().
*
@ -206,7 +203,7 @@ public class MemorizingTrustManager {
/**
* Removes an Activity from the MTM display stack.
* <p>
*
* Always call this function when the Activity added with
* {@link #bindDisplayActivity(Activity)} is hidden.
*
@ -220,11 +217,11 @@ public class MemorizingTrustManager {
/**
* Changes the path for the KeyStore file.
* <p>
*
* The actual filename relative to the app's directory will be
* <code>app_<i>dirname</i>/<i>filename</i></code>.
*
* @param dirname directory to store the KeyStore.
* @param dirname directory to store the KeyStore.
* @param filename file name for the KeyStore.
*/
public static void setKeyStoreFile(String dirname, String filename) {
@ -250,6 +247,7 @@ public class MemorizingTrustManager {
* Get a certificate for a given alias.
*
* @param alias the certificate's alias as returned by {@link #getCertificates()}.
*
* @return the certificate associated with the alias or <tt>null</tt> if none found.
*/
public Certificate getCertificate(String alias) {
@ -263,15 +261,15 @@ public class MemorizingTrustManager {
/**
* Removes the given certificate from MTMs key store.
* <p>
*
* <p>
* <b>WARNING</b>: this does not immediately invalidate the certificate. It is
* well possible that (a) data is transmitted over still existing connections or
* (b) new connections are created using TLS renegotiation, without a new cert
* check.
* </p>
*
* @param alias the certificate's alias as returned by {@link #getCertificates()}.
*
* @throws KeyStoreException if the certificate could not be deleted.
*/
public void deleteCertificate(String alias) throws KeyStoreException {
@ -281,27 +279,28 @@ public class MemorizingTrustManager {
/**
* Creates a new hostname verifier supporting user interaction.
* <p>
*
* <p>This method creates a new {@link HostnameVerifier} that is bound to
* the given instance of {@link MemorizingTrustManager}, and leverages an
* existing {@link HostnameVerifier}. The returned verifier performs the
* following steps, returning as soon as one of them succeeds:
* </p>
* <ol>
* <li>Success, if the wrapped defaultVerifier accepts the certificate.</li>
* <li>Success, if the server certificate is stored in the keystore under the given hostname.</li>
* <li>Ask the user and return accordingly.</li>
* <li>Failure on exception.</li>
* </ol>
* </p>
* <ol>
* <li>Success, if the wrapped defaultVerifier accepts the certificate.</li>
* <li>Success, if the server certificate is stored in the keystore under the given hostname.</li>
* <li>Ask the user and return accordingly.</li>
* <li>Failure on exception.</li>
* </ol>
*
* @param defaultVerifier the {@link HostnameVerifier} that should perform the actual check
* @return a new hostname verifier using the MTM's key store
*
* @throws IllegalArgumentException if the defaultVerifier parameter is null
*/
public DomainHostnameVerifier wrapHostnameVerifier(final HostnameVerifier defaultVerifier, final boolean interactive) {
if (defaultVerifier == null) {
if (defaultVerifier == null)
throw new IllegalArgumentException("The default verifier may not be null");
}
return new MemorizingHostnameVerifier(defaultVerifier, interactive);
}
@ -311,7 +310,7 @@ public class MemorizingTrustManager {
tmf.init(ks);
for (TrustManager t : tmf.getTrustManagers()) {
if (t instanceof X509TrustManager) {
return (X509TrustManager) t;
return (X509TrustManager)t;
}
}
} catch (Exception e) {
@ -397,7 +396,8 @@ public class MemorizingTrustManager {
}
public void checkCertTrusted(X509Certificate[] chain, String authType, String domain, boolean isServer, boolean interactive)
throws CertificateException {
throws CertificateException
{
LOGGER.log(Level.FINE, "checkCertTrusted(" + chain + ", " + authType + ", " + isServer + ")");
try {
LOGGER.log(Level.FINE, "checkCertTrusted: trying appTrustManager");
@ -427,10 +427,10 @@ public class MemorizingTrustManager {
} catch (CertificateException e) {
boolean trustSystemCAs = !PreferenceManager.getDefaultSharedPreferences(master).getBoolean("dont_trust_system_cas", false);
if (domain != null && isServer && trustSystemCAs && !isIp(domain)) {
String hash = getBase64Hash(chain[0], "SHA-256");
String hash = getBase64Hash(chain[0],"SHA-256");
List<String> fingerprints = getPoshFingerprints(domain);
if (hash != null && fingerprints.contains(hash)) {
Log.d("mtm", "trusted cert fingerprint of " + domain + " via posh");
Log.d("mtm","trusted cert fingerprint of "+domain+" via posh");
return;
}
}
@ -454,11 +454,11 @@ public class MemorizingTrustManager {
}
private List<String> getPoshFingerprintsFromServer(String domain) {
return getPoshFingerprintsFromServer(domain, "https://" + domain + "/.well-known/posh/xmpp-client.json", -1, true);
return getPoshFingerprintsFromServer(domain, "https://"+domain+"/.well-known/posh/xmpp-client.json",-1,true);
}
private List<String> getPoshFingerprintsFromServer(String domain, String url, int maxTtl, boolean followUrl) {
Log.d("mtm", "downloading json for " + domain + " from " + url);
Log.d("mtm","downloading json for "+domain+" from "+url);
try {
List<String> results = new ArrayList<>();
HttpsURLConnection connection = (HttpsURLConnection) new URL(url).openConnection();
@ -477,7 +477,7 @@ public class MemorizingTrustManager {
return new ArrayList<>();
}
if (maxTtl >= 0) {
expires = Math.min(maxTtl, expires);
expires = Math.min(maxTtl,expires);
}
String redirect;
try {
@ -489,23 +489,23 @@ public class MemorizingTrustManager {
return getPoshFingerprintsFromServer(domain, redirect, expires, false);
}
JSONArray fingerprints = jsonObject.getJSONArray("fingerprints");
for (int i = 0; i < fingerprints.length(); i++) {
for(int i = 0; i < fingerprints.length(); i++) {
JSONObject fingerprint = fingerprints.getJSONObject(i);
String sha256 = fingerprint.getString("sha-256");
if (sha256 != null) {
results.add(sha256);
}
}
writeFingerprintsToCache(domain, results, 1000L * expires + System.currentTimeMillis());
writeFingerprintsToCache(domain, results,1000L * expires+System.currentTimeMillis());
return results;
} catch (Exception e) {
Log.d("mtm", "error fetching posh " + e.getMessage());
Log.d("mtm","error fetching posh "+e.getMessage());
return new ArrayList<>();
}
}
private File getPoshCacheFile(String domain) {
return new File(poshCacheDir + domain + ".json");
return new File(poshCacheDir+domain+".json");
}
private void writeFingerprintsToCache(String domain, List<String> results, long expires) {
@ -514,8 +514,8 @@ public class MemorizingTrustManager {
try {
file.createNewFile();
JSONObject jsonObject = new JSONObject();
jsonObject.put("expires", expires);
jsonObject.put("fingerprints", new JSONArray(results));
jsonObject.put("expires",expires);
jsonObject.put("fingerprints",new JSONArray(results));
FileOutputStream outputStream = new FileOutputStream(file);
outputStream.write(jsonObject.toString().getBytes());
outputStream.flush();
@ -534,7 +534,7 @@ public class MemorizingTrustManager {
String line = buf.readLine();
StringBuilder sb = new StringBuilder();
while (line != null) {
while(line != null){
sb.append(line).append("\n");
line = buf.readLine();
}
@ -546,11 +546,11 @@ public class MemorizingTrustManager {
file.delete();
return null;
} else {
Log.d("mtm", "posh fingerprints expire in " + (expiresIn / 1000) + "s");
Log.d("mtm","posh fingerprints expire in "+(expiresIn/1000)+"s");
}
List<String> result = new ArrayList<>();
JSONArray jsonArray = jsonObject.getJSONArray("fingerprints");
for (int i = 0; i < jsonArray.length(); ++i) {
for(int i = 0; i < jsonArray.length(); ++i) {
result.add(jsonArray.getString(i));
}
return result;
@ -581,7 +581,7 @@ public class MemorizingTrustManager {
return null;
}
md.update(certificate.getEncoded());
return Base64.encodeToString(md.digest(), Base64.NO_WRAP);
return Base64.encodeToString(md.digest(),Base64.NO_WRAP);
}
private X509Certificate[] getAcceptedIssuers() {
@ -591,7 +591,7 @@ public class MemorizingTrustManager {
private int createDecisionId(MTMDecision d) {
int myId;
synchronized (openDecisions) {
synchronized(openDecisions) {
myId = decisionId;
openDecisions.put(myId, d);
decisionId += 1;
@ -676,7 +676,7 @@ public class MemorizingTrustManager {
Object name = altName.get(1);
if (name instanceof String) {
si.append("[");
si.append((Integer) altName.get(0));
si.append((Integer)altName.get(0));
si.append("] ");
si.append(name);
si.append("\n");
@ -695,7 +695,6 @@ public class MemorizingTrustManager {
certDetails(si, cert);
return si.toString();
}
/**
* Returns the top-most entry of the activity stack.
*
@ -706,7 +705,7 @@ public class MemorizingTrustManager {
}
int interact(final String message, final int titleId) {
/* prepare the MTMDecision blocker object */
/* prepare the MTMDecision blocker object */
MTMDecision choice = new MTMDecision();
final int myId = createDecisionId(choice);
@ -731,9 +730,7 @@ public class MemorizingTrustManager {
LOGGER.log(Level.FINE, "openDecisions: " + openDecisions + ", waiting on " + myId);
try {
synchronized (choice) {
choice.wait();
}
synchronized(choice) { choice.wait(); }
} catch (InterruptedException e) {
LOGGER.log(Level.FINER, "InterruptedException", e);
}
@ -742,7 +739,8 @@ public class MemorizingTrustManager {
}
void interactCert(final X509Certificate[] chain, String authType, CertificateException cause)
throws CertificateException {
throws CertificateException
{
switch (interact(certChainMessage(chain, cause), R.string.mtm_accept_cert)) {
case MTMDecision.DECISION_ALWAYS:
storeCert(chain[0]); // only store the server cert, not the whole chain
@ -753,7 +751,8 @@ public class MemorizingTrustManager {
}
}
boolean interactHostname(X509Certificate cert, String hostname) {
boolean interactHostname(X509Certificate cert, String hostname)
{
switch (interact(hostNameMessage(cert, hostname), R.string.mtm_accept_servername)) {
case MTMDecision.DECISION_ALWAYS:
storeCert(hostname, cert);
@ -764,9 +763,9 @@ public class MemorizingTrustManager {
}
}
protected static void interactResult(int decisionId, int choice) {
public static void interactResult(int decisionId, int choice) {
MTMDecision d;
synchronized (openDecisions) {
synchronized(openDecisions) {
d = openDecisions.get(decisionId);
openDecisions.remove(decisionId);
}
@ -774,7 +773,7 @@ public class MemorizingTrustManager {
LOGGER.log(Level.SEVERE, "interactResult: aborting due to stale decision reference!");
return;
}
synchronized (d) {
synchronized(d) {
d.state = choice;
d.notify();
}
@ -794,7 +793,7 @@ public class MemorizingTrustManager {
LOGGER.log(Level.FINE, "hostname verifier for " + domain + ", trying default verifier first");
// if the default verifier accepts the hostname, we are done
if (defaultVerifier instanceof DomainHostnameVerifier) {
if (((DomainHostnameVerifier) defaultVerifier).verify(domain, hostname, session)) {
if (((DomainHostnameVerifier) defaultVerifier).verify(domain,hostname, session)) {
return true;
}
} else {
@ -803,9 +802,10 @@ public class MemorizingTrustManager {
}
}
// otherwise, we check if the hostname is an alias for this cert in our keystore
try {
X509Certificate cert = (X509Certificate) session.getPeerCertificates()[0];
X509Certificate cert = (X509Certificate)session.getPeerCertificates()[0];
//Log.d(TAG, "cert: " + cert);
if (cert.equals(appKeyStore.getCertificate(domain.toLowerCase(Locale.US)))) {
LOGGER.log(Level.FINE, "certificate for " + domain + " is in our keystore. accepting.");
@ -826,10 +826,11 @@ public class MemorizingTrustManager {
@Override
public boolean verify(String domain, SSLSession sslSession) {
return verify(domain, null, sslSession);
return verify(domain,null,sslSession);
}
}
public X509TrustManager getNonInteractive(String domain) {
return new NonInteractiveMemorizingTrustManager(domain);
}
@ -895,4 +896,4 @@ public class MemorizingTrustManager {
return MemorizingTrustManager.this.getAcceptedIssuers();
}
}
}
}

View file

@ -70,7 +70,6 @@ import java.util.concurrent.CountDownLatch;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicLong;
import de.duenndns.ssl.MemorizingTrustManager;
import de.pixart.messenger.BuildConfig;
import de.pixart.messenger.Config;
import de.pixart.messenger.R;

View file

@ -21,11 +21,8 @@
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
package de.duenndns.ssl;
package de.pixart.messenger.ui;
import android.app.Activity;
import android.app.AlertDialog;
import android.content.DialogInterface;
import android.content.DialogInterface.OnCancelListener;
import android.content.DialogInterface.OnClickListener;
@ -33,15 +30,20 @@ import android.content.Intent;
import android.content.SharedPreferences;
import android.os.Bundle;
import android.preference.PreferenceManager;
import android.support.v7.app.AlertDialog;
import android.support.v7.app.AppCompatActivity;
import java.util.logging.Level;
import java.util.logging.Logger;
public class MemorizingActivity extends Activity
implements OnClickListener, OnCancelListener {
import de.pixart.messenger.R;
import de.pixart.messenger.entities.MTMDecision;
import de.pixart.messenger.services.MemorizingTrustManager;
public class MemorizingActivity extends AppCompatActivity implements OnClickListener,OnCancelListener {
public static final String THEME = "theme";
private final static Logger LOGGER = Logger.getLogger(MemorizingActivity.class.getName());
int decisionId;
AlertDialog dialog;
@ -49,7 +51,7 @@ public class MemorizingActivity extends Activity
@Override
public void onCreate(Bundle savedInstanceState) {
LOGGER.log(Level.FINE, "onCreate");
// setTheme(findTheme());
//setTheme(findTheme());
super.onCreate(savedInstanceState);
}
@ -63,9 +65,9 @@ public class MemorizingActivity extends Activity
LOGGER.log(Level.FINE, "onResume with " + i.getExtras() + " decId=" + decisionId + " data: " + i.getData());
dialog = new AlertDialog.Builder(this).setTitle(titleId)
.setMessage(cert)
.setPositiveButton(R.string.mtm_decision_always, this)
.setNeutralButton(R.string.mtm_decision_once, this)
.setNegativeButton(R.string.mtm_decision_abort, this)
.setPositiveButton(R.string.always, this)
.setNeutralButton(R.string.once, this)
.setNegativeButton(R.string.cancel, this)
.setOnCancelListener(this)
.create();
dialog.show();
@ -84,12 +86,14 @@ public class MemorizingActivity extends Activity
finish();
}
// protected int findTheme() {
// return getPreferences().getString(THEME, getResources().getString(R.string.theme)).equals("dark") ? R.style.ConversationsTheme_Dark : R.style.ConversationsTheme;
// }
protected int findTheme() {
return 0;
//return getPreferences().getString(SettingsActivity.THEME, getResources().getString(R.string.theme)).equals("dark") ? R.style.ConversationsTheme_Dark : R.style.ConversationsTheme;
}
protected SharedPreferences getPreferences() {
return PreferenceManager.getDefaultSharedPreferences(getApplicationContext());
return PreferenceManager
.getDefaultSharedPreferences(getApplicationContext());
}
// react on AlertDialog button press
@ -112,4 +116,4 @@ public class MemorizingActivity extends Activity
public void onCancel(DialogInterface dialog) {
sendDecision(MTMDecision.DECISION_ABORT);
}
}
}

View file

@ -29,7 +29,7 @@ import java.util.Collections;
import java.util.List;
import java.util.Locale;
import de.duenndns.ssl.MemorizingTrustManager;
import de.pixart.messenger.services.MemorizingTrustManager;
import de.pixart.messenger.Config;
import de.pixart.messenger.R;
import de.pixart.messenger.entities.Account;

View file

@ -50,8 +50,8 @@ import javax.net.ssl.SSLSocketFactory;
import javax.net.ssl.X509KeyManager;
import javax.net.ssl.X509TrustManager;
import de.duenndns.ssl.DomainHostnameVerifier;
import de.duenndns.ssl.MemorizingTrustManager;
import de.pixart.messenger.crypto.DomainHostnameVerifier;
import de.pixart.messenger.services.MemorizingTrustManager;
import de.pixart.messenger.Config;
import de.pixart.messenger.crypto.XmppDomainVerifier;
import de.pixart.messenger.crypto.axolotl.AxolotlService;

View file

@ -763,4 +763,14 @@
<string name="huawei_protected_apps_summary">To keep receiving notifications, even when the screen is turned off, you need to add Pix-Art Messenger to the list of protected apps.</string>
<string name="pref_enable_multi_accounts_title">Enable multiple accounts</string>
<string name="pref_enable_multi_accounts_summary">You want to use multiple accounts, so you have to set a password for daily backups.</string>
<string name="mtm_accept_cert">Accept Unknown Certificate?</string>
<string name="mtm_trust_anchor">The server certificate is not signed by a known Certificate Authority.</string>
<string name="mtm_cert_expired">The server certificate is expired.</string>
<string name="mtm_accept_servername">Accept Mismatching Server Name?</string>
<string name="mtm_hostname_mismatch">Server could not authenticate as \&quot;%s\&quot;. The certificate is only valid for:</string>
<string name="mtm_connect_anyway">Do you want to connect anyway?</string>
<string name="mtm_cert_details">Certificate details:</string>
<string name="mtm_notification">Certificate Verification</string>
<string name="once">Once</string>
<string name="theme">Theme</string>
</resources>