diff options
Diffstat (limited to 'OpenKeychain')
5 files changed, 198 insertions, 88 deletions
diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/Constants.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/Constants.java index 319ac2873..1ddb3d632 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/Constants.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/Constants.java @@ -49,6 +49,41 @@ public final class Constants { public static final String CUSTOM_CONTACT_DATA_MIME_TYPE = "vnd.android.cursor.item/vnd.org.sufficientlysecure.keychain.key"; + // TODO: Resource/Asset? + public static final String SKS_KEYSERVERS_NET_CA = + "-----BEGIN CERTIFICATE-----\n" + + "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\n" + + "-----END CERTIFICATE-----"; + public static boolean KITKAT = Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT; public static final class Path { @@ -67,10 +102,12 @@ public final class Constants { public static final String LANGUAGE = "language"; public static final String FORCE_V3_SIGNATURES = "forceV3Signatures"; public static final String KEY_SERVERS = "keyServers"; + public static final String KEY_SERVERS_DEFAULT_VERSION = "keyServersDefaultVersion"; } public static final class Defaults { - public static final String KEY_SERVERS = "pool.sks-keyservers.net, subkeys.pgp.net, pgp.mit.edu"; + public static final String KEY_SERVERS = "hkps://hkps.pool.sks-keyservers.net, subkeys.pgp.net, hkps://pgp.mit.edu"; + public static final int KEY_SERVERS_VERSION = 2; } public static final class DrawerItems { diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/KeychainApplication.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/KeychainApplication.java index 5d6a62f9c..9d355564f 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/KeychainApplication.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/KeychainApplication.java @@ -26,7 +26,7 @@ import android.graphics.drawable.Drawable; import android.os.Environment; import org.spongycastle.jce.provider.BouncyCastleProvider; -import org.sufficientlysecure.keychain.helper.ContactHelper; +import org.sufficientlysecure.keychain.helper.Preferences; import org.sufficientlysecure.keychain.util.Log; import org.sufficientlysecure.keychain.util.PRNGFixes; @@ -81,6 +81,9 @@ public class KeychainApplication extends Application { getApplicationContext().getResources().getColor(R.color.emphasis)); setupAccountAsNeeded(this); + + // Update keyserver list as needed + Preferences.getPreferences(this).updateKeyServers(); } public static void setupAccountAsNeeded(Context context) { diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/helper/Preferences.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/helper/Preferences.java index c82c5e115..6f3d38ccd 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/helper/Preferences.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/helper/Preferences.java @@ -169,4 +169,22 @@ public class Preferences { editor.putString(Constants.Pref.KEY_SERVERS, rawData); editor.commit(); } + + public void updateKeyServers() { + if (mSharedPreferences.getInt(Constants.Pref.KEY_SERVERS_DEFAULT_VERSION, 0) != + Constants.Defaults.KEY_SERVERS_VERSION) { + String[] servers = getKeyServers(); + for (int i = 0; i < servers.length; i++) { + if (servers[i].equals("pool.sks-keyservers.net")) { + servers[i] = "hkps://hkps.pool.sks-keyservers.net"; + } else if (servers[i].equals("pgp.mit.edu")) { + servers[i] = "hkps://pgp.mit.edu"; + } + } + setKeyServers(servers); + mSharedPreferences.edit() + .putInt(Constants.Pref.KEY_SERVERS_DEFAULT_VERSION, Constants.Defaults.KEY_SERVERS_VERSION) + .commit(); + } + } } diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/helper/TlsHelper.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/helper/TlsHelper.java new file mode 100644 index 000000000..96df7cf2f --- /dev/null +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/helper/TlsHelper.java @@ -0,0 +1,83 @@ +/* + * Copyright (C) 2013-2014 Dominik Schürmann <dominik@dominikschuermann.de> + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +package org.sufficientlysecure.keychain.helper; + +import javax.net.ssl.HttpsURLConnection; +import javax.net.ssl.SSLContext; +import javax.net.ssl.TrustManagerFactory; +import java.io.ByteArrayInputStream; +import java.io.IOException; +import java.net.URL; +import java.security.KeyManagementException; +import java.security.KeyStore; +import java.security.KeyStoreException; +import java.security.NoSuchAlgorithmException; +import java.security.cert.*; + +public class TlsHelper { + public static class TlsHelperException extends Exception { + public TlsHelperException(Exception e) { + super(e); + } + } + + /** + * Opens a Connection that will only accept certificates signed with a specific CA and skips common name check. + * This is required for some distributed Keyserver networks like sks-keyservers.net + * + * @param certificate The X.509 certificate used to sign the servers certificate + * @param url Connection target + */ + public static HttpsURLConnection openCAConnection(byte[] certificate, URL url) + throws TlsHelperException, IOException { + try { + // Load CA + CertificateFactory cf = CertificateFactory.getInstance("X.509"); + Certificate ca = cf.generateCertificate(new ByteArrayInputStream(certificate)); + + // Create a KeyStore containing our trusted CAs + String keyStoreType = KeyStore.getDefaultType(); + KeyStore keyStore = KeyStore.getInstance(keyStoreType); + keyStore.load(null, null); + keyStore.setCertificateEntry("ca", ca); + + // Create a TrustManager that trusts the CAs in our KeyStore + String tmfAlgorithm = TrustManagerFactory.getDefaultAlgorithm(); + TrustManagerFactory tmf = TrustManagerFactory.getInstance(tmfAlgorithm); + tmf.init(keyStore); + + // Create an SSLContext that uses our TrustManager + SSLContext context = SSLContext.getInstance("TLS"); + context.init(null, tmf.getTrustManagers(), null); + + // Tell the URLConnection to use a SocketFactory from our SSLContext + HttpsURLConnection urlConnection = (HttpsURLConnection) url.openConnection(); + urlConnection.setSSLSocketFactory(context.getSocketFactory()); + + return urlConnection; + } catch (CertificateException e) { + throw new TlsHelperException(e); + } catch (NoSuchAlgorithmException e) { + throw new TlsHelperException(e); + } catch (KeyStoreException e) { + throw new TlsHelperException(e); + } catch (KeyManagementException e) { + throw new TlsHelperException(e); + } + } +} diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/keyimport/HkpKeyserver.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/keyimport/HkpKeyserver.java index e49085a0e..b048a2cea 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/keyimport/HkpKeyserver.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/keyimport/HkpKeyserver.java @@ -22,37 +22,23 @@ import de.measite.minidns.Client; import de.measite.minidns.Question; import de.measite.minidns.Record; import de.measite.minidns.record.SRV; -import org.apache.http.HttpEntity; -import org.apache.http.HttpResponse; -import org.apache.http.HttpStatus; -import org.apache.http.NameValuePair; -import org.apache.http.client.HttpClient; -import org.apache.http.client.entity.UrlEncodedFormEntity; -import org.apache.http.client.methods.HttpGet; -import org.apache.http.client.methods.HttpPost; -import org.apache.http.impl.client.DefaultHttpClient; -import org.apache.http.message.BasicNameValuePair; -import org.apache.http.util.EntityUtils; import org.sufficientlysecure.keychain.Constants; +import org.sufficientlysecure.keychain.helper.TlsHelper; import org.sufficientlysecure.keychain.pgp.PgpHelper; import org.sufficientlysecure.keychain.pgp.PgpKeyHelper; import org.sufficientlysecure.keychain.util.Log; +import java.io.DataOutputStream; import java.io.IOException; -import java.io.InputStream; import java.io.UnsupportedEncodingException; import java.net.HttpURLConnection; -import java.net.InetAddress; -import java.net.MalformedURLException; import java.net.URL; import java.net.URLDecoder; import java.net.URLEncoder; -import java.net.UnknownHostException; import java.util.ArrayList; import java.util.Arrays; import java.util.Comparator; import java.util.GregorianCalendar; -import java.util.List; import java.util.Locale; import java.util.TimeZone; import java.util.regex.Matcher; @@ -200,48 +186,39 @@ public class HkpKeyserver extends Keyserver { return mSecure ? "https://" : "http://"; } - private String query(String request) throws QueryFailedException, HttpError { - List<String> urls = new ArrayList<String>(); - if (mSecure) { - urls.add(getUrlPrefix() + mHost + ":" + mPort + request); - } else { - InetAddress ips[]; + private HttpURLConnection openConnection(URL url) throws IOException { + HttpURLConnection conn = null; + if (mHost.endsWith("pool.sks-keyservers.net") && mSecure) { try { - ips = InetAddress.getAllByName(mHost); - } catch (UnknownHostException e) { - throw new QueryFailedException(e.toString()); - } - for (InetAddress ip : ips) { - // Note: This is actually not HTTP 1.1 compliant, as we hide the real "Host" value, - // but Android's HTTPUrlConnection does not support any other way to set - // Socket's remote IP address... - urls.add(getUrlPrefix() + ip.getHostAddress() + ":" + mPort + request); + conn = TlsHelper.openCAConnection(Constants.SKS_KEYSERVERS_NET_CA.getBytes(), url); + } catch (TlsHelper.TlsHelperException e) { + Log.w(Constants.TAG, e); } } + if (conn == null) { + conn = (HttpURLConnection) url.openConnection(); + } + conn.setConnectTimeout(5000); + conn.setReadTimeout(25000); + return conn; + } - for (String url : urls) { - try { - Log.d(Constants.TAG, "hkp keyserver query: " + url); - URL realUrl = new URL(url); - HttpURLConnection conn = (HttpURLConnection) realUrl.openConnection(); - conn.setConnectTimeout(5000); - conn.setReadTimeout(25000); - conn.connect(); - int response = conn.getResponseCode(); - if (response >= 200 && response < 300) { - return readAll(conn.getInputStream(), conn.getContentEncoding()); - } else { - String data = readAll(conn.getErrorStream(), conn.getContentEncoding()); - throw new HttpError(response, data); - } - } catch (MalformedURLException e) { - // nothing to do, try next IP - } catch (IOException e) { - // nothing to do, try next IP + private String query(String request) throws QueryFailedException, HttpError { + try { + URL url = new URL(getUrlPrefix() + mHost + ":" + mPort + request); + Log.d(Constants.TAG, "hkp keyserver query: " + url); + HttpURLConnection conn = openConnection(url); + conn.connect(); + int response = conn.getResponseCode(); + if (response >= 200 && response < 300) { + return readAll(conn.getInputStream(), conn.getContentEncoding()); + } else { + String data = readAll(conn.getErrorStream(), conn.getContentEncoding()); + throw new HttpError(response, data); } + } catch (IOException e) { + throw new QueryFailedException("querying server(s) for '" + mHost + "' failed"); } - - throw new QueryFailedException("querying server(s) for '" + mHost + "' failed"); } @Override @@ -341,52 +318,44 @@ public class HkpKeyserver extends Keyserver { @Override public String get(String keyIdHex) throws QueryFailedException { - HttpClient client = new DefaultHttpClient(); + String request = "/pks/lookup?op=get&options=mr&search=" + keyIdHex; + Log.d(Constants.TAG, "hkp keyserver get: " + request); + String data; try { - String query = getUrlPrefix() + mHost + ":" + mPort + - "/pks/lookup?op=get&options=mr&search=" + keyIdHex; - Log.d(Constants.TAG, "hkp keyserver get: " + query); - HttpGet get = new HttpGet(query); - HttpResponse response = client.execute(get); - if (response.getStatusLine().getStatusCode() != HttpStatus.SC_OK) { - throw new QueryFailedException("not found"); - } - - HttpEntity entity = response.getEntity(); - InputStream is = entity.getContent(); - String data = readAll(is, EntityUtils.getContentCharSet(entity)); - Matcher matcher = PgpHelper.PGP_PUBLIC_KEY.matcher(data); - if (matcher.find()) { - return matcher.group(1); - } - } catch (IOException e) { - // nothing to do, better luck on the next keyserver - } finally { - client.getConnectionManager().shutdown(); + data = query(request); + } catch (HttpError httpError) { + throw new QueryFailedException("not found"); + } + Matcher matcher = PgpHelper.PGP_PUBLIC_KEY.matcher(data); + if (matcher.find()) { + return matcher.group(1); } - return null; } @Override public void add(String armoredKey) throws AddKeyException { - HttpClient client = new DefaultHttpClient(); try { String query = getUrlPrefix() + mHost + ":" + mPort + "/pks/add"; - HttpPost post = new HttpPost(query); - Log.d(Constants.TAG, "hkp keyserver add: " + query); - List<NameValuePair> nameValuePairs = new ArrayList<NameValuePair>(2); - nameValuePairs.add(new BasicNameValuePair("keytext", armoredKey)); - post.setEntity(new UrlEncodedFormEntity(nameValuePairs)); - - HttpResponse response = client.execute(post); - if (response.getStatusLine().getStatusCode() != HttpStatus.SC_OK) { + String params; + try { + params = "keytext=" + URLEncoder.encode(armoredKey, "utf8"); + } catch (UnsupportedEncodingException e) { throw new AddKeyException(); } + Log.d(Constants.TAG, "hkp keyserver add: " + query); + + HttpURLConnection connection = openConnection(new URL(query)); + connection.setRequestMethod("POST"); + connection.addRequestProperty("Content-Type", "application/x-www-form-urlencoded"); + connection.setRequestProperty("Content-Length", Integer.toString(params.getBytes().length)); + connection.setDoOutput(true); + DataOutputStream wr = new DataOutputStream(connection.getOutputStream()); + wr.writeBytes(params); + wr.flush(); + wr.close(); } catch (IOException e) { - // nothing to do, better luck on the next keyserver - } finally { - client.getConnectionManager().shutdown(); + throw new AddKeyException(); } } |