integrate cert4android

This commit is contained in:
schaarsc 2017-04-23 00:27:06 +02:00 committed by Niedermann IT-Dienstleistungen
parent 6ec8da66d0
commit 231de0a64c
15 changed files with 230 additions and 59 deletions

3
.gitmodules vendored Normal file
View file

@ -0,0 +1,3 @@
[submodule "cert4android"]
path = cert4android
url = https://gitlab.com/bitfireAT/cert4android.git

View file

@ -24,6 +24,8 @@ android {
}
dependencies {
compile project(':cert4android')
compile 'com.yydcdut:rxmarkdown:0.1.0'
compile 'com.android.support:support-v4:25.3.1'
compile 'com.android.support:appcompat-v7:25.3.1'

View file

@ -15,9 +15,9 @@ public class NotesClientUtilTest extends TestCase {
}
public void testIsValidURLTest() {
assertTrue(NotesClientUtil.isValidURL("https://demo.owncloud.org/"));
assertFalse(NotesClientUtil.isValidURL("https://www.example.com/"));
assertFalse(NotesClientUtil.isValidURL("htp://www.example.com/"));
assertFalse(NotesClientUtil.isValidURL(null));
assertTrue(NotesClientUtil.isValidURL(null, "https://demo.owncloud.org/"));
assertFalse(NotesClientUtil.isValidURL(null, "https://www.example.com/"));
assertFalse(NotesClientUtil.isValidURL(null, "htp://www.example.com/"));
assertFalse(NotesClientUtil.isValidURL(null, null));
}
}

View file

@ -21,6 +21,7 @@ import android.widget.EditText;
import android.widget.TextView;
import android.widget.Toast;
import at.bitfire.cert4android.CustomCertManager;
import it.niedermann.owncloud.notes.R;
import it.niedermann.owncloud.notes.persistence.NoteServerSyncHelper;
import it.niedermann.owncloud.notes.util.NotesClientUtil;
@ -47,8 +48,10 @@ public class SettingsActivity extends AppCompatActivity {
private TextInputLayout password_wrapper = null;
private String old_password = "";
private Button btn_submit = null;
private Button btn_cert_reset = null;
private boolean first_run = false;
private boolean showNotification = false;
private boolean trustSystemCerts = true;
@Override
public void onCreate(Bundle savedInstanceState) {
@ -77,6 +80,29 @@ public class SettingsActivity extends AppCompatActivity {
}
});
trustSystemCerts = preferences.getBoolean("trustSystemCerts", true);
cb = (CheckBox)findViewById(R.id.settings_cert_trust_system);
cb.setChecked(trustSystemCerts);
cb.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() {
@Override
public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
SharedPreferences.Editor editor = preferences.edit();
editor.putBoolean("trustSystemCerts", isChecked);
editor.commit();
}
});
btn_cert_reset = (Button) findViewById(R.id.settings_cert_reset);
btn_cert_reset.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
CustomCertManager ccm = new CustomCertManager(getApplicationContext(), true);
ccm.resetCertificates();
ccm.close();
Toast.makeText(getApplicationContext(), getString(R.string.settings_cert_reset_toast), Toast.LENGTH_SHORT).show();
}
});
field_url = (EditText) findViewById(R.id.settings_url);
field_username = (EditText) findViewById(R.id.settings_username);
field_password = (EditText) findViewById(R.id.settings_password);
@ -215,7 +241,7 @@ public class SettingsActivity extends AppCompatActivity {
@Override
protected Boolean doInBackground(String... params) {
return NotesClientUtil.isValidURL(params[0]);
return NotesClientUtil.isValidURL(getApplicationContext(), params[0]);
}
@Override
@ -251,7 +277,7 @@ public class SettingsActivity extends AppCompatActivity {
url = params[0];
username = params[1];
password = params[2];
return NotesClientUtil.isValidLogin(url, username, password);
return NotesClientUtil.isValidLogin(getApplicationContext(), url, username, password);
}
@Override

View file

@ -222,7 +222,7 @@ public class NoteServerSyncHelper {
if (note.getRemoteId()>0) {
Log.d(getClass().getSimpleName(), " ...try to edit");
try {
remoteNote = client.editNote(note);
remoteNote = client.editNote(appContext, note);
} catch(FileNotFoundException e) {
// Note does not exists anymore
}
@ -231,7 +231,7 @@ public class NoteServerSyncHelper {
// Please note, thas dbHelper.updateNote() realizes an optimistic conflict resolution, which is required for parallel changes of this Note from the UI.
if (remoteNote == null) {
Log.d(getClass().getSimpleName(), " ...Note does not exist on server -> (re)create");
remoteNote = client.createNote(note);
remoteNote = client.createNote(appContext, note);
}
dbHelper.updateNote(note.getId(), remoteNote, note);
break;
@ -239,7 +239,7 @@ public class NoteServerSyncHelper {
if(note.getRemoteId()>0) {
Log.d(getClass().getSimpleName(), " ...delete (from server and local)");
try {
client.deleteNote(note.getRemoteId());
client.deleteNote(appContext, note.getRemoteId());
} catch (FileNotFoundException e) {
Log.d(getClass().getSimpleName(), " ...Note does not exist on server (anymore?) -> delete locally");
}
@ -267,7 +267,7 @@ public class NoteServerSyncHelper {
LoginStatus status = null;
try {
Map<Long, Long> idMap = dbHelper.getIdMap();
List<CloudNote> remoteNotes = client.getNotes();
List<CloudNote> remoteNotes = client.getNotes(appContext);
Set<Long> remoteIDs = new HashSet<>();
// pull remote changes: update or create each remote note
for (CloudNote remoteNote : remoteNotes) {

View file

@ -1,5 +1,6 @@
package it.niedermann.owncloud.notes.util;
import android.content.Context;
import android.util.Base64;
import android.util.Log;
@ -13,12 +14,12 @@ import java.io.InputStreamReader;
import java.io.OutputStream;
import java.net.HttpURLConnection;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.GregorianCalendar;
import java.util.List;
import at.bitfire.cert4android.CustomCertManager;
import it.niedermann.owncloud.notes.model.CloudNote;
public class NotesClient {
@ -78,9 +79,9 @@ public class NotesClient {
return new CloudNote(id, modified, title, content, favorite, category, etag);
}
public List<CloudNote> getNotes() throws JSONException, IOException {
public List<CloudNote> getNotes(Context ctx) throws JSONException, IOException {
List<CloudNote> notesList = new ArrayList<>();
JSONArray notes = new JSONArray(requestServer("notes", METHOD_GET, null));
JSONArray notes = new JSONArray(requestServer(ctx, "notes", METHOD_GET, null));
for (int i = 0; i < notes.length(); i++) {
JSONObject json = notes.getJSONObject(i);
notesList.add(getNoteFromJSON(json));
@ -97,17 +98,17 @@ public class NotesClient {
* @throws IOException
*/
@SuppressWarnings("unused")
public CloudNote getNoteById(long id) throws JSONException, IOException {
JSONObject json = new JSONObject(requestServer("notes/" + id, METHOD_GET, null));
public CloudNote getNoteById(Context ctx, long id) throws JSONException, IOException {
JSONObject json = new JSONObject(requestServer(ctx, "notes/" + id, METHOD_GET, null));
return getNoteFromJSON(json);
}
private CloudNote putNote(CloudNote note, String path, String method) throws JSONException, IOException {
private CloudNote putNote(Context ctx, CloudNote note, String path, String method) throws JSONException, IOException {
JSONObject paramObject = new JSONObject();
paramObject.accumulate(key_content, note.getContent());
paramObject.accumulate(key_modified, note.getModified().getTimeInMillis()/1000);
paramObject.accumulate(key_favorite, note.isFavorite());
JSONObject json = new JSONObject(requestServer(path, method, paramObject));
JSONObject json = new JSONObject(requestServer(ctx, path, method, paramObject));
return getNoteFromJSON(json);
}
@ -119,17 +120,17 @@ public class NotesClient {
* @throws JSONException
* @throws IOException
*/
public CloudNote createNote(CloudNote note) throws JSONException, IOException {
return putNote(note, "notes", METHOD_POST);
public CloudNote createNote(Context ctx, CloudNote note) throws JSONException, IOException {
return putNote(ctx, note, "notes", METHOD_POST);
}
public CloudNote editNote(CloudNote note) throws JSONException, IOException {
return putNote(note, "notes/" + note.getRemoteId(), METHOD_PUT);
public CloudNote editNote(Context ctx, CloudNote note) throws JSONException, IOException {
return putNote(ctx, note, "notes/" + note.getRemoteId(), METHOD_PUT);
}
public void deleteNote(long noteId) throws
public void deleteNote(Context ctx, long noteId) throws
IOException {
this.requestServer("notes/" + noteId, METHOD_DELETE, null);
this.requestServer(ctx, "notes/" + noteId, METHOD_DELETE, null);
}
/**
@ -142,34 +143,39 @@ public class NotesClient {
* @throws MalformedURLException
* @throws IOException
*/
private String requestServer(String target, String method, JSONObject params)
private String requestServer(Context ctx, String target, String method, JSONObject params)
throws IOException {
StringBuffer result = new StringBuffer();
String targetURL = url + "index.php/apps/notes/api/v0.2/" + target;
HttpURLConnection con = (HttpURLConnection) new URL(targetURL)
.openConnection();
con.setRequestMethod(method);
con.setRequestProperty(
"Authorization",
"Basic " + Base64.encodeToString((username + ":" + password).getBytes(), Base64.NO_WRAP));
con.setConnectTimeout(10 * 1000); // 10 seconds
Log.d(getClass().getSimpleName(), method + " " + targetURL);
if (params != null) {
byte[] paramData = params.toString().getBytes();
Log.d(getClass().getSimpleName(), "Params: "+params);
con.setFixedLengthStreamingMode(paramData.length);
con.setRequestProperty("Content-Type", application_json);
con.setDoOutput(true);
OutputStream os = con.getOutputStream();
os.write(paramData);
os.flush();
os.close();
}
BufferedReader rd = new BufferedReader(new InputStreamReader(con.getInputStream()));
String line;
while ((line = rd.readLine()) != null) {
result.append(line);
CustomCertManager ccm = SupportUtil.getCertManager(ctx);
try {
HttpURLConnection con = SupportUtil.getHttpURLConnection(ccm, targetURL);
con.setRequestMethod(method);
con.setRequestProperty(
"Authorization",
"Basic " + Base64.encodeToString((username + ":" + password).getBytes(), Base64.NO_WRAP));
con.setConnectTimeout(10 * 1000); // 10 seconds
Log.d(getClass().getSimpleName(), method + " " + targetURL);
if (params != null) {
byte[] paramData = params.toString().getBytes();
Log.d(getClass().getSimpleName(), "Params: " + params);
con.setFixedLengthStreamingMode(paramData.length);
con.setRequestProperty("Content-Type", application_json);
con.setDoOutput(true);
OutputStream os = con.getOutputStream();
os.write(paramData);
os.flush();
os.close();
}
BufferedReader rd = new BufferedReader(new InputStreamReader(con.getInputStream()));
String line;
while ((line = rd.readLine()) != null) {
result.append(line);
}
} finally {
ccm.close();
}
return result.toString();
}
}

View file

@ -1,5 +1,6 @@
package it.niedermann.owncloud.notes.util;
import android.content.Context;
import android.util.Base64;
import android.util.Log;
@ -12,8 +13,8 @@ import java.io.IOException;
import java.io.InputStreamReader;
import java.net.HttpURLConnection;
import java.net.MalformedURLException;
import java.net.URL;
import at.bitfire.cert4android.CustomCertManager;
import it.niedermann.owncloud.notes.R;
/**
@ -52,11 +53,11 @@ public class NotesClientUtil {
* @param password String
* @return Username and Password are a valid Login-Combination for the given URL.
*/
public static LoginStatus isValidLogin(String url, String username, String password) {
public static LoginStatus isValidLogin(Context ctx, String url, String username, String password) {
CustomCertManager ccm = SupportUtil.getCertManager(ctx);
try {
String targetURL = url + "index.php/apps/notes/api/v0.2/notes";
HttpURLConnection con = (HttpURLConnection) new URL(targetURL)
.openConnection();
HttpURLConnection con = SupportUtil.getHttpURLConnection(ccm, targetURL);
con.setRequestMethod("GET");
con.setRequestProperty(
"Authorization",
@ -89,6 +90,8 @@ public class NotesClientUtil {
} catch (JSONException e) {
Log.e(NotesClientUtil.class.getSimpleName(), "Exception", e);
return LoginStatus.JSON_FAILED;
} finally {
ccm.close();
}
}
@ -98,11 +101,11 @@ public class NotesClientUtil {
* @param url String URL to server
* @return true if there is a installed instance, false if not
*/
public static boolean isValidURL(String url) {
public static boolean isValidURL(Context ctx, String url) {
StringBuilder result = new StringBuilder();
CustomCertManager ccm = SupportUtil.getCertManager(ctx);
try {
HttpURLConnection con = (HttpURLConnection) new URL(url + "status.php")
.openConnection();
HttpURLConnection con = SupportUtil.getHttpURLConnection(ccm, url + "status.php");
con.setRequestMethod(NotesClient.METHOD_GET);
con.setConnectTimeout(10 * 1000); // 10 seconds
BufferedReader rd = new BufferedReader(new InputStreamReader(con.getInputStream()));
@ -114,6 +117,8 @@ public class NotesClientUtil {
return response.getBoolean("installed");
} catch (IOException | JSONException | NullPointerException e) {
return false;
} finally {
ccm.close();
}
}

View file

@ -1,11 +1,28 @@
package it.niedermann.owncloud.notes.util;
import android.content.Context;
import android.content.SharedPreferences;
import android.os.Build;
import android.preference.PreferenceManager;
import android.text.Html;
import android.text.Spanned;
import android.text.method.LinkMovementMethod;
import android.widget.TextView;
import java.io.IOException;
import java.net.HttpURLConnection;
import java.net.MalformedURLException;
import java.net.URL;
import java.security.KeyManagementException;
import java.security.NoSuchAlgorithmException;
import javax.net.ssl.HostnameVerifier;
import javax.net.ssl.HttpsURLConnection;
import javax.net.ssl.SSLContext;
import javax.net.ssl.TrustManager;
import at.bitfire.cert4android.CustomCertManager;
/**
* Some helper functionality in alike the Android support library.
* Currently, it offers methods for working with HTML string resources.
@ -37,4 +54,37 @@ public class SupportUtil {
view.setText(SupportUtil.fromHtml(view.getResources().getString(stringId, formatArgs)));
view.setMovementMethod(LinkMovementMethod.getInstance());
}
/**
* Create a new {@link HttpURLConnection} for strUrl.
* If protocol equals https, then install CustomCertManager in {@link SSLContext}.
* @param ccm
* @param strUrl
* @return HttpURLConnection with custom trust manager
* @throws MalformedURLException
* @throws IOException
*/
public static HttpURLConnection getHttpURLConnection(CustomCertManager ccm, String strUrl) throws MalformedURLException, IOException {
URL url = new URL(strUrl);
HttpURLConnection httpCon = (HttpURLConnection) url.openConnection();
if (ccm != null && url.getProtocol().equals("https")) {
HttpsURLConnection httpsCon = (HttpsURLConnection) httpCon;
httpsCon.setHostnameVerifier(ccm.hostnameVerifier(httpsCon.getHostnameVerifier()));
try {
SSLContext sslContext = SSLContext.getInstance("TLS");
sslContext.init(null, new TrustManager[]{ccm}, null);
httpsCon.setSSLSocketFactory(sslContext.getSocketFactory());
} catch (NoSuchAlgorithmException e) {
// ignore, use default TrustManager
} catch (KeyManagementException e) {
// ignore, use default TrustManager
}
}
return httpCon;
}
public static CustomCertManager getCertManager(Context ctx) {
SharedPreferences preferences = PreferenceManager.getDefaultSharedPreferences(ctx);
return new CustomCertManager(ctx, preferences.getBoolean("trustSystemCerts", true));
}
}

View file

@ -8,6 +8,14 @@
android:paddingLeft="16dp"
android:paddingRight="16dp">
<TextView
android:id="@+id/settings_server_header"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@string/settings_server_header"
android:textSize="18sp"
android:textStyle="bold" />
<android.support.design.widget.TextInputLayout
android:id="@+id/settings_url_wrapper"
android:layout_width="match_parent"
@ -70,6 +78,15 @@
android:shadowColor="@color/fg_default_low"
android:text="@string/settings_submit" />
<TextView
android:id="@+id/settings_notification_header"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:paddingTop="10dp"
android:text="@string/settings_notification_header"
android:textSize="18sp"
android:textStyle="bold" />
<CheckBox
android:id="@+id/settings_notification"
android:layout_width="wrap_content"
@ -77,4 +94,25 @@
android:defaultValue="false"
android:key="notificationCheckbox"
android:text="@string/settings_notification" />
</LinearLayout>
<TextView
android:id="@+id/settings_cert_header"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:paddingTop="20dp"
android:text="@string/settings_cert_header"
android:textSize="18sp"
android:textStyle="bold" />
<CheckBox
android:id="@+id/settings_cert_trust_system"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@string/settings_cert_trust_system" />
<Button
android:id="@+id/settings_cert_reset"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@string/settings_cert_reset" />
</LinearLayout>

View file

@ -35,6 +35,7 @@
<string name="listview_updated_earlier">Früher</string>
<!-- Settings -->
<string name="settings_server_header">Server Einstellungen</string>
<string name="settings_server">Server</string>
<string name="settings_url">Server-Adresse</string>
<string name="settings_url_check_description">Zeigt an, ob die angegebene URL erreichbar ist.</string>
@ -44,7 +45,16 @@
<string name="settings_password_check_description">Zeigt an, ob die angegebenen Zugangsdaten korrekt sind.</string>
<string name="settings_submit">Verbinden</string>
<string name="settings_submitting">Verbindet&#8230;</string>
<string name="settings_notification_header">Benachrichtigung</string>
<string name="settings_notification">Aktivieren, um eine dauerhafte Benachrichtigung zur schnellen Eingabe von Notizen anzuzeigen.</string>
<string name="settings_cert_header">Zertifikate</string>
<string name="settings_cert_trust_system">Zertifikaten aus System Trust Store vertrauen.</string>
<string name="settings_cert_reset">Bisher akzeptierte/abgelehnte Zertifikate zurücksetzen</string>
<string name="settings_cert_reset_toast">Informationen über Zertifikate zurückgesetzt.</string>
<!-- Certificates -->
<string name="certificate_notification_connection_security">Notizen - Verbindungssicherheit</string>
<string name="trust_certificate_unknown_certificate_found">Der Server nutzt ein unbekanntes Zertifikat. Soll diesem Zertifikat vertraut werden?</string>
<!-- Network -->
<string name="network_connecting">Verbinde</string>
@ -98,4 +108,4 @@
<item quantity="one">%d ausgewählt</item>
<item quantity="other">%d ausgewählt</item>
</plurals>
</resources>
</resources>

View file

@ -35,6 +35,7 @@
<string name="listview_updated_earlier">Früher</string>
<!-- Settings -->
<string name="settings_server_header">Server Einstellungen</string>
<string name="settings_server">Server</string>
<string name="settings_url">Server-Adresse</string>
<string name="settings_url_check_description">Zeigt an, ob die angegebene URL erreichbar ist.</string>
@ -44,7 +45,16 @@
<string name="settings_password_check_description">Zeigt an, ob die angegebenen Zugangsdaten korrekt sind.</string>
<string name="settings_submit">Verbinden</string>
<string name="settings_submitting">Verbindet&#8230;</string>
<string name="settings_notification_header">Benachrichtigung</string>
<string name="settings_notification">Aktivieren um eine dauerhafte Benachrichtigung zur schnellen Eingabe von Notizen anzuzeigen.</string>
<string name="settings_cert_header">Zertifikate</string>
<string name="settings_cert_trust_system">Zertifikaten aus System Trust Store vertrauen.</string>
<string name="settings_cert_reset">Bisher akzeptierte/abgelehnte Zertifikate zurücksetzen</string>
<string name="settings_cert_reset_toast">Informationen über Zertifikate zurückgesetzt.</string>
<!-- Certificates -->
<string name="certificate_notification_connection_security">Notizen - Verbindungssicherheit</string>
<string name="trust_certificate_unknown_certificate_found">Der Server nutzt ein unbekanntes Zertifikat. Soll diesem Zertifikat vertraut werden?</string>
<!-- Network -->
<string name="network_connecting">Verbindung wird hergestellt</string>
@ -98,4 +108,4 @@
<item quantity="one">%d ausgewählt</item>
<item quantity="other">%d ausgewählt</item>
</plurals>
</resources>
</resources>

View file

@ -35,6 +35,7 @@
<string name="listview_updated_earlier">Earlier</string>
<!-- Settings -->
<string name="settings_server_header">Server Settings</string>
<string name="settings_server">Server</string>
<string name="settings_url">Server address</string>
<string name="settings_url_check_description">Shows if the address can be pinged.</string>
@ -45,7 +46,16 @@
<string name="settings_password_check_description">Shows if the credentials are correct.</string>
<string name="settings_submit">Connect</string>
<string name="settings_submitting">Connecting &#8230;</string>
<string name="settings_notification_header">Notification</string>
<string name="settings_notification">Enable to show a persistant notification for quickly creating new notes.</string>
<string name="settings_cert_header">Certificates</string>
<string name="settings_cert_trust_system">Trust certificates from system trust store.</string>
<string name="settings_cert_reset">Reset trust store</string>
<string name="settings_cert_reset_toast">Stored trust information cleared.</string>
<!-- Certificates -->
<string name="certificate_notification_connection_security">Notes - Connection security</string>
<string name="trust_certificate_unknown_certificate_found">Notes has encountered an unknown certificate. Do you want to trust it?</string>
<!-- Network -->
<string name="network_connecting">Connecting</string>
@ -109,4 +119,4 @@
<item quantity="one">%d selected</item>
<item quantity="other">%d selected</item>
</plurals>
</resources>
</resources>

View file

@ -13,6 +13,15 @@
<item name="android:windowBackground">@color/bg_normal</item>
</style>
<!--
cert4android is using these styles and we want the same button style in the trust activity
-->
<style name="Widget.AppCompat.Button.Borderless" parent="ocbutton">
<item name="android:layout_margin">2dp</item>
</style>
<style name="Widget.AppCompat.Button.Borderless.Colored" parent="Widget.AppCompat.Button.Borderless">
</style>
<style name="fab">
<item name="android:layout_margin">16dp</item>
<item name="android:layout_width">wrap_content</item>

1
cert4android Submodule

@ -0,0 +1 @@
Subproject commit 9033f39d1f541b62750c2bfa9c93a893d6af2f63

View file

@ -1 +1,2 @@
include ':app'
include ':app'
include ':cert4android'