diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/keyimport/HkpKeyserverClient.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/keyimport/HkpKeyserverClient.java index 78e63825b..7100a91f3 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/keyimport/HkpKeyserverClient.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/keyimport/HkpKeyserverClient.java @@ -18,6 +18,18 @@ package org.sufficientlysecure.keychain.keyimport; +import android.support.annotation.NonNull; +import org.sufficientlysecure.keychain.network.OkHttpClientFactory; +import org.sufficientlysecure.keychain.pgp.PgpHelper; +import org.sufficientlysecure.keychain.ui.util.KeyFormattingUtils; +import org.sufficientlysecure.keychain.util.ParcelableProxy; +import timber.log.Timber; +import okhttp3.FormBody; +import okhttp3.HttpUrl; +import okhttp3.OkHttpClient; +import okhttp3.Request; +import okhttp3.RequestBody; +import okhttp3.Response; import java.io.IOException; import java.io.UnsupportedEncodingException; import java.net.Proxy; @@ -33,24 +45,47 @@ import java.util.TimeZone; import java.util.regex.Matcher; import java.util.regex.Pattern; -import android.support.annotation.NonNull; - -import okhttp3.FormBody; -import okhttp3.HttpUrl; -import okhttp3.OkHttpClient; -import okhttp3.Request; -import okhttp3.RequestBody; -import okhttp3.Response; -import org.sufficientlysecure.keychain.network.OkHttpClientFactory; -import org.sufficientlysecure.keychain.pgp.PgpHelper; -import org.sufficientlysecure.keychain.ui.util.KeyFormattingUtils; -import org.sufficientlysecure.keychain.util.ParcelableProxy; -import timber.log.Timber; - import static java.util.Locale.ENGLISH; public class HkpKeyserverClient implements KeyserverClient { + + private static final Pattern INFO_LINE = Pattern + .compile("^info:1:([0-9]*)\n", Pattern.CASE_INSENSITIVE); + + /** + * uid:%escaped uid string%:%creationdate%:%expirationdate%:%flags% + * + */ + private static final Pattern UID_LINE = Pattern + .compile("(?uid:" + // group 1 + "(?[^:\n]*)" + // group 2 + "(?::(?[0-9]*)" + // group 3 + "(?::(?[0-9]*)" + // group 4 + "(?::(?((?=(r(?!(.?r))|d(?!(.?d))|e(?!(.?e))))[rde]){0,3})" + // group 5 + ")?)?)?\n)", + Pattern.CASE_INSENSITIVE); + /** * pub:%keyid%:%algo%:%keylen%:%creationdate%:%expirationdate%:%flags% *
    @@ -83,39 +118,19 @@ public class HkpKeyserverClient implements KeyserverClient { * in Internet-Draft OpenPGP HTTP Keyserver Protocol Document */ private static final Pattern PUB_KEY_LINE = Pattern - .compile("pub:([0-9a-fA-F]+):([0-9]+):([0-9]+):([0-9]+):([0-9]*):([rde]*)[ \n\r]*" // pub line - + "((uid:([^:]*):([0-9]+):([0-9]*):([rde]*)[ \n\r]*)+)", // one or more uid lines + .compile( "(?pub:" + // group 1 + "(?[0-9a-fA-F]+)" + // group 2 + "(?::(?[0-9]*)" + // group 3 + "(?::(?[0-9]*)" + // group 4 + "(?::(?[0-9]*)" + // group 5 + "(?::(?[0-9]*)" + // group 6 + "(?::(?(?:(?=(?:r(?!(.?r))|d(?!(.?d))|e(?!(.?e))))[rde]){0,3})" + // group 7 + ")?)?)?)?)?\n)"// pub line + + "(?" + UID_LINE.pattern() + // group 11 + "+)", // one or more uid lines Pattern.CASE_INSENSITIVE ); - /** - * uid:%escaped uid string%:%creationdate%:%expirationdate%:%flags% - *
      - *
    • %escaped uid string% = the user ID string, with HTTP %-escaping for anything that - * isn't 7-bit safe as well as for the ":" character. Any other characters may be escaped, as - * desired.
    • - *
    • %creationdate% = creation date of the key in standard - * RFC-2440 form (i.e. number of - * seconds since 1/1/1970 UTC time)
    • - *
    • %expirationdate% = expiration date of the key in standard - * RFC-2440 form (i.e. number of - * seconds since 1/1/1970 UTC time)
    • - *
    • %flags% = letter codes to indicate details of the key, if any. Flags may be in any - * order. The meaning of "disabled" is implementation-specific. Note that individual flags may - * be unimplemented, so the absence of a given flag does not necessarily mean the absence of - * the detail. - *
        - *
      • r == revoked
      • - *
      • d == disabled
      • - *
      • e == expired
      • - *
      - *
    • - *
    - */ - private static final Pattern UID_LINE = Pattern - .compile("uid:([^:]*):([0-9]+):([0-9]*):([rde]*)", - Pattern.CASE_INSENSITIVE); - private static final Charset UTF_8 = Charset.forName("utf-8"); @@ -155,26 +170,33 @@ public class HkpKeyserverClient implements KeyserverClient { } catch (URISyntaxException e) { throw new IllegalStateException("Unsupported keyserver URI"); } catch (HttpError e) { - if (e.getData() != null) { + String errData = ""; + if (e.getData() != null) { // Some servers return an error message Timber.d("returned error data: " + e.getData().toLowerCase(ENGLISH)); - - if (e.getData().toLowerCase(Locale.ENGLISH).contains("no keys found")) { - // NOTE: This is also a 404 error for some keyservers! - return results; - } else if (e.getData().toLowerCase(Locale.ENGLISH).contains("too many")) { - throw new KeyserverClient.TooManyResponsesException(); - } else if (e.getData().toLowerCase(Locale.ENGLISH).contains("insufficient")) { - throw new KeyserverClient.QueryTooShortException(); - } else if (e.getCode() == 404) { - // NOTE: handle this 404 at last, maybe it was a "no keys found" error - throw new KeyserverClient.QueryFailedException("Keyserver '" + hkpKeyserver.getUrl() + "' not found. Error 404"); - } else { - // NOTE: some keyserver do not provide a more detailed error response - throw new KeyserverClient.QueryTooShortOrTooManyResponsesException(); - } + errData = e.getData().toLowerCase(ENGLISH); } + if (e.code == 501) { + // https://tools.ietf.org/html/draft-shaw-openpgp-hkp-00#section-3.1.1: + // "If any [...] searching is not supported, [...] error code such as 501 + throw new KeyserverClient.QueryNotImplementedException(); + } else if (e.code == 404 || errData.contains("no keys found")) { + return results; + } else if (errData.contains("too many")) { + throw new KeyserverClient.TooManyResponsesException(); + } else if (errData.contains("insufficient")) { + throw new KeyserverClient.QueryTooShortException(); + } else { + // NOTE: some keyserver do not provide a more detailed error response + throw new KeyserverClient.QueryTooShortOrTooManyResponsesException(); + } + } - throw new KeyserverClient.QueryFailedException("Querying server(s) for '" + hkpKeyserver.getUrl() + "' failed."); + final Matcher infoMatcher = INFO_LINE.matcher(data); + if (infoMatcher.find()) { + int numFound = Integer.parseInt(infoMatcher.group(1)); + Timber.d("Server returned " + numFound + " public key(s)"); + } else { + Timber.w("Server using non-standard hkp"); } final Matcher matcher = PUB_KEY_LINE.matcher(data); @@ -182,9 +204,9 @@ public class HkpKeyserverClient implements KeyserverClient { final ImportKeysListEntry entry = new ImportKeysListEntry(); entry.setQuery(query); - // group 1 contains the full fingerprint (v4) or the long key id if available + // group 2 contains the full fingerprint (v4) or the long key id if available // see https://bitbucket.org/skskeyserver/sks-keyserver/pull-request/12/fixes-for-machine-readable-indexes/diff - String fingerprintOrKeyId = matcher.group(1).toLowerCase(Locale.ENGLISH); + String fingerprintOrKeyId = matcher.group(2).toLowerCase(Locale.ENGLISH); if (fingerprintOrKeyId.length() == 40) { byte[] fingerprint = KeyFormattingUtils.convertFingerprintHexFingerprint(fingerprintOrKeyId); entry.setFingerprint(fingerprint); @@ -200,15 +222,24 @@ public class HkpKeyserverClient implements KeyserverClient { } try { - int bitSize = Integer.parseInt(matcher.group(3)); - entry.setBitStrength(bitSize); - int algorithmId = Integer.decode(matcher.group(2)); - entry.setAlgorithm(KeyFormattingUtils.getAlgorithmInfo(algorithmId, bitSize, null)); + int bitSize = -1; + if (!matcher.group(4).isEmpty()){ // empty fields are allowed + bitSize = Integer.parseInt(matcher.group(4)); + entry.setBitStrength(bitSize); + } - long creationDate = Long.parseLong(matcher.group(4)); - GregorianCalendar calendar = new GregorianCalendar(TimeZone.getTimeZone("UTC")); - calendar.setTimeInMillis(creationDate * 1000); - entry.setDate(calendar.getTime()); + if (!matcher.group(3).isEmpty()){ // empty fields are allowed + int algorithmId = Integer.decode(matcher.group(3)); + entry.setAlgorithm(KeyFormattingUtils + .getAlgorithmInfo(algorithmId, bitSize, null)); + } + + if (!matcher.group(5).isEmpty()) { // empty fields are allowed + long creationDate = Long.parseLong(matcher.group(5)); + GregorianCalendar calendar = new GregorianCalendar(TimeZone.getTimeZone("UTC")); + calendar.setTimeInMillis(creationDate * 1000); + entry.setDate(calendar.getTime()); + } } catch (NumberFormatException e) { Timber.e(e, "Conversation for bit size, algorithm, or creation date failed."); // skip this key @@ -216,12 +247,12 @@ public class HkpKeyserverClient implements KeyserverClient { } try { - entry.setRevoked(matcher.group(6).contains("r")); - boolean expired = matcher.group(6).contains("e"); + entry.setRevoked(matcher.group(7).contains("r")); + boolean expired = matcher.group(7).contains("e"); // It may be expired even without flag, thus check expiration date String expiration; - if (!expired && !(expiration = matcher.group(5)).isEmpty()) { + if (!expired && !(expiration = matcher.group(6)).isEmpty()) { long expirationDate = Long.parseLong(expiration); TimeZone timeZoneUTC = TimeZone.getTimeZone("UTC"); GregorianCalendar calendar = new GregorianCalendar(timeZoneUTC); @@ -236,10 +267,10 @@ public class HkpKeyserverClient implements KeyserverClient { } ArrayList userIds = new ArrayList<>(); - final String uidLines = matcher.group(7); + final String uidLines = matcher.group(11); final Matcher uidMatcher = UID_LINE.matcher(uidLines); while (uidMatcher.find()) { - String tmp = uidMatcher.group(1).trim(); + String tmp = uidMatcher.group(2).trim(); if (tmp.contains("%")) { if (tmp.contains("%%")) { // The server encodes a percent sign as %%, so it is swapped out with its diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/keyimport/KeyserverClient.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/keyimport/KeyserverClient.java index f8ddc0f86..351781971 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/keyimport/KeyserverClient.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/keyimport/KeyserverClient.java @@ -18,9 +18,6 @@ package org.sufficientlysecure.keychain.keyimport; import org.sufficientlysecure.keychain.util.ParcelableProxy; -import java.io.ByteArrayOutputStream; -import java.io.IOException; -import java.io.InputStream; import java.util.List; public interface KeyserverClient { @@ -57,6 +54,10 @@ public interface KeyserverClient { private static final long serialVersionUID = 2693768928624654512L; } + class QueryNotImplementedException extends QueryNeedsRepairException { + private static final long serialVersionUID = 8126381739806854079L; + } + class TooManyResponsesException extends QueryNeedsRepairException { private static final long serialVersionUID = 2703768928624654513L; } diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/keyimport/processing/ImportKeysListCloudLoader.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/keyimport/processing/ImportKeysListCloudLoader.java index a6ac852c2..6833343ac 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/keyimport/processing/ImportKeysListCloudLoader.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/keyimport/processing/ImportKeysListCloudLoader.java @@ -20,11 +20,11 @@ package org.sufficientlysecure.keychain.keyimport.processing; import android.content.Context; import android.support.annotation.Nullable; import android.support.v4.content.AsyncTaskLoader; - import org.sufficientlysecure.keychain.keyimport.CloudSearch; import org.sufficientlysecure.keychain.keyimport.ImportKeysListEntry; import org.sufficientlysecure.keychain.keyimport.KeyserverClient; import org.sufficientlysecure.keychain.keyimport.ParcelableKeyRing; +import org.sufficientlysecure.keychain.network.orbot.OrbotHelper; import org.sufficientlysecure.keychain.operations.results.GetKeyResult; import org.sufficientlysecure.keychain.operations.results.OperationResult; import org.sufficientlysecure.keychain.service.input.CryptoInputParcel; @@ -32,7 +32,6 @@ import org.sufficientlysecure.keychain.service.input.RequiredInputParcel; import org.sufficientlysecure.keychain.ui.util.KeyFormattingUtils; import org.sufficientlysecure.keychain.util.ParcelableProxy; import org.sufficientlysecure.keychain.util.Preferences; -import org.sufficientlysecure.keychain.network.orbot.OrbotHelper; import timber.log.Timber; import java.util.ArrayList; @@ -172,7 +171,10 @@ public class ImportKeysListCloudLoader // convert exception to result parcel int error = GetKeyResult.RESULT_ERROR; OperationResult.LogType logType = null; - if (e instanceof KeyserverClient.QueryFailedException) { + if (e instanceof KeyserverClient.QueryNotImplementedException){ + error = GetKeyResult.RESULT_ERROR_QUERY_NOT_IMPLEMENTED; + logType = OperationResult.LogType.MSG_GET_QUERY_NOT_IMPLEMENTED; + } else if (e instanceof KeyserverClient.QueryFailedException) { error = GetKeyResult.RESULT_ERROR_QUERY_FAILED; logType = OperationResult.LogType.MSG_GET_QUERY_FAILED; } else if (e instanceof KeyserverClient.TooManyResponsesException) { diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/results/GetKeyResult.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/results/GetKeyResult.java index 0139a06d3..e7c5da99f 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/results/GetKeyResult.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/results/GetKeyResult.java @@ -18,7 +18,6 @@ package org.sufficientlysecure.keychain.operations.results; import android.os.Parcel; - import org.sufficientlysecure.keychain.service.input.CryptoInputParcel; import org.sufficientlysecure.keychain.service.input.RequiredInputParcel; @@ -51,6 +50,7 @@ public class GetKeyResult extends InputPendingResult { public static final int RESULT_ERROR_QUERY_FAILED = RESULT_ERROR + (6 << 4); public static final int RESULT_ERROR_FILE_NOT_FOUND = RESULT_ERROR + (7 << 4); public static final int RESULT_ERROR_NO_ENABLED_SOURCE = RESULT_ERROR + (8 << 4); + public static final int RESULT_ERROR_QUERY_NOT_IMPLEMENTED = RESULT_ERROR + (9 << 4); public GetKeyResult(Parcel source) { super(source); diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/results/OperationResult.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/results/OperationResult.java index 966ae84d4..1320ac282 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/results/OperationResult.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/results/OperationResult.java @@ -23,7 +23,6 @@ import android.content.res.Resources; import android.os.Parcel; import android.os.Parcelable; import android.support.annotation.NonNull; - import org.sufficientlysecure.keychain.R; import org.sufficientlysecure.keychain.ui.LogDisplayActivity; import org.sufficientlysecure.keychain.ui.LogDisplayFragment; @@ -805,6 +804,7 @@ public abstract class OperationResult implements Parcelable { MSG_GET_TOO_MANY_RESPONSES (LogLevel.ERROR, R.string.msg_get_too_many_responses), MSG_GET_QUERY_TOO_SHORT_OR_TOO_MANY_RESPONSES (LogLevel.ERROR, R.string.msg_get_query_too_short_or_too_many_responses), MSG_GET_QUERY_FAILED (LogLevel.ERROR, R.string.msg_download_query_failed), + MSG_GET_QUERY_NOT_IMPLEMENTED (LogLevel.ERROR, R.string.msg_get_query_not_implemented), MSG_GET_FILE_NOT_FOUND (LogLevel.ERROR, R.string.msg_get_file_not_found), MSG_GET_NO_ENABLED_SOURCE (LogLevel.ERROR, R.string.msg_get_no_enabled_source), diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/adapter/ImportKeysAdapter.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/adapter/ImportKeysAdapter.java index 9acab30ce..3c110e571 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/adapter/ImportKeysAdapter.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/adapter/ImportKeysAdapter.java @@ -18,11 +18,6 @@ package org.sufficientlysecure.keychain.ui.adapter; -import java.io.IOException; -import java.util.ArrayList; -import java.util.Date; -import java.util.List; - import android.content.Intent; import android.databinding.DataBindingUtil; import android.support.v4.app.FragmentActivity; @@ -31,7 +26,6 @@ import android.view.LayoutInflater; import android.view.View; import android.view.View.OnClickListener; import android.view.ViewGroup; - import org.sufficientlysecure.keychain.R; import org.sufficientlysecure.keychain.databinding.ImportKeysListItemBinding; import org.sufficientlysecure.keychain.keyimport.HkpKeyserverAddress; @@ -48,13 +42,18 @@ import org.sufficientlysecure.keychain.pgp.exception.PgpKeyNotFoundException; import org.sufficientlysecure.keychain.provider.KeyRepository; import org.sufficientlysecure.keychain.provider.KeychainContract.KeyRings; import org.sufficientlysecure.keychain.service.ImportKeyringParcel; -import org.sufficientlysecure.keychain.ui.keyview.ViewKeyActivity; import org.sufficientlysecure.keychain.ui.base.CryptoOperationHelper; +import org.sufficientlysecure.keychain.ui.keyview.ViewKeyActivity; import org.sufficientlysecure.keychain.ui.util.KeyFormattingUtils; import org.sufficientlysecure.keychain.ui.util.Notify; import org.sufficientlysecure.keychain.util.ParcelableFileCache; import timber.log.Timber; +import java.io.IOException; +import java.util.ArrayList; +import java.util.Date; +import java.util.List; + public class ImportKeysAdapter extends RecyclerView.Adapter implements ImportKeysResultListener { @@ -292,6 +291,15 @@ public class ImportKeysAdapter extends RecyclerView.AdapterTo import your existing setup from another device, you can also open an Autocrypt Setup Message in %s. Go to OpenKeychain I have written down this backup code. Without it, I will be unable to restore the backup. + The server does not support the current query! Some servers only accept mailaddresses. Please redefine or try a different server.