/* * Kontalk Android client * Copyright (C) 2017 Kontalk Devteam <devteam@kontalk.org> * 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.kontalk.provider; import java.io.IOException; import java.util.Date; import java.util.HashMap; import java.util.Iterator; import java.util.Map; import org.spongycastle.openpgp.PGPException; import org.spongycastle.openpgp.PGPPublicKey; import org.spongycastle.openpgp.PGPPublicKeyRing; import android.content.ContentValues; import android.content.Context; import android.database.Cursor; import android.text.TextUtils; import org.kontalk.client.EndpointServer; import org.kontalk.crypto.Coder; import org.kontalk.crypto.PGP; import org.kontalk.crypto.PGPCoder; import org.kontalk.crypto.PersonalKey; /** * Keyring management. * @author Daniele Ricci */ public class Keyring { private Keyring() { } /** Returns a {@link Coder} instance for encrypting data. */ public static Coder getEncryptCoder(Context context, EndpointServer server, PersonalKey key, String[] recipients) { // get recipients public keys from users database PGPPublicKeyRing keys[] = new PGPPublicKeyRing[recipients.length]; for (int i = 0; i < recipients.length; i++) { PGPPublicKeyRing ring = getPublicKey(context, recipients[i], MyUsers.Keys.TRUST_IGNORED); if (ring == null) throw new IllegalArgumentException("public key not found for user " + recipients[i]); keys[i] = ring; } return new PGPCoder(server, key, keys); } /** Returns a {@link Coder} instance for decrypting data. */ public static Coder getDecryptCoder(Context context, EndpointServer server, PersonalKey key, String sender) { PGPPublicKeyRing senderKey = getPublicKey(context, sender, MyUsers.Keys.TRUST_IGNORED); return new PGPCoder(server, key, senderKey); } /** Returns a {@link Coder} instance for verifying data. */ public static Coder getVerifyCoder(Context context, EndpointServer server, String sender) { PGPPublicKeyRing senderKey = getPublicKey(context, sender, MyUsers.Keys.TRUST_UNKNOWN); return new PGPCoder(server, null, senderKey); } /** Adds/updates a public key. */ public static void setKey(Context context, String jid, byte[] keydata) throws IOException, PGPException { setKey(context, jid, keydata, -1); } /** Adds/updates a public key. */ public static void setKey(Context context, String jid, byte[] keydata, int trustLevel) throws IOException, PGPException { PGPPublicKey pk = PGP.getMasterKey(keydata); String fingerprint = PGP.getFingerprint(pk); Date date = pk.getCreationTime(); ContentValues values = new ContentValues(3); values.put(MyUsers.Keys.PUBLIC_KEY, keydata); values.put(MyUsers.Keys.TIMESTAMP, date.getTime()); if (trustLevel >= 0) values.put(MyUsers.Keys.TRUST_LEVEL, trustLevel); context.getContentResolver().insert(MyUsers.Keys.getUri(jid, fingerprint), values); } /** Updates the fingerprint and the date (for fingerprint in presence). */ public static void setKey(Context context, String jid, String fingerprint, Date date) { setKey(context, jid, fingerprint, date, -1); } /** Updates the fingerprint and the date (for fingerprint in presence). */ public static void setKey(Context context, String jid, String fingerprint, Date date, int trustLevel) { ContentValues values = new ContentValues(2); values.put(MyUsers.Keys.TIMESTAMP, date.getTime()); if (trustLevel >= 0) values.put(MyUsers.Keys.TRUST_LEVEL, trustLevel); context.getContentResolver().insert(MyUsers.Keys.getUri(jid, fingerprint) // since we are handling data from a presence, insert only if it doesn't exist .buildUpon().appendQueryParameter(MyUsers.Keys.INSERT_ONLY, "true").build(), values); } /** Sets the trust level for the given key. */ public static void setTrustLevel(Context context, String jid, String fingerprint, int trustLevel) { ContentValues values = new ContentValues(1); values.put(MyUsers.Keys.TRUST_LEVEL, trustLevel); context.getContentResolver().insert(MyUsers.Keys.getUri(jid, fingerprint), values); } /** * Retrieves the latest fingerprint with the minimum given trust level. * @param trustLevel the minimum trust level to consider */ public static String getFingerprint(Context context, String jid, int trustLevel) { String fingerprint = null; Cursor c = queryLatestWithMinimumTrustLevel(context, jid, trustLevel, MyUsers.Keys.FINGERPRINT); if (c.moveToFirst()) fingerprint = c.getString(0); c.close(); return fingerprint; } /** * Retrieves the latest public key with the minimum given trust level. * @param trustLevel the minimum trust level to consider */ public static PGPPublicKeyRing getPublicKey(Context context, String jid, int trustLevel) { TrustedPublicKeyData key = getPublicKeyData(context, jid, trustLevel); try { return PGP.readPublicKeyring(key.keyData); } catch (Exception e) { // ignored } return null; } /** * Retrieves the latest public key with the minimum given trust level. * @param trustLevel the minimum trust level to consider */ public static TrustedPublicKeyData getPublicKeyData(Context context, String jid, int trustLevel) { TrustedPublicKeyData data = null; Cursor c = queryLatestWithMinimumTrustLevel(context, jid, trustLevel, MyUsers.Keys.PUBLIC_KEY, MyUsers.Keys.TRUST_LEVEL); if (c.moveToFirst()) { byte[] keydata = c.getBlob(0); int trustStatus = c.getInt(1); if (keydata != null) data = new TrustedPublicKeyData(keydata, trustStatus); } c.close(); return data; } private static Cursor queryLatestWithMinimumTrustLevel(Context context, String jid, int trustLevel, String... columns) { return context.getContentResolver().query(MyUsers.Keys.getUri(jid), columns, MyUsers.Keys.TRUST_LEVEL + " >= " + trustLevel, null, MyUsers.Keys.TIMESTAMP + " DESC"); } /** Sets the trusted keys, deleting all previous entries. */ public static int setTrustedKeys(Context context, Map<String, TrustedFingerprint> trustedKeys) { ContentValues[] values = new ContentValues[trustedKeys.size()]; Iterator<Map.Entry<String, TrustedFingerprint>> entries = trustedKeys.entrySet().iterator(); for (int i = 0; i < values.length; i++) { Map.Entry<String, TrustedFingerprint> e = entries.next(); values[i] = new ContentValues(2); values[i].put(MyUsers.Keys.JID, e.getKey()); values[i].put(MyUsers.Keys.FINGERPRINT, e.getValue().fingerprint); values[i].put(MyUsers.Keys.TRUST_LEVEL, e.getValue().trustLevel); } return context.getContentResolver().bulkInsert(MyUsers.Keys.CONTENT_URI, values); } /** Returns a JID-fingerprint map of trusted keys. */ public static Map<String, TrustedFingerprint> getTrustedKeys(Context context) { Cursor c = context.getContentResolver().query(MyUsers.Keys.CONTENT_URI, new String[] { MyUsers.Keys.JID, MyUsers.Keys.FINGERPRINT, MyUsers.Keys.TRUST_LEVEL, }, null, null, null); Map<String, TrustedFingerprint> list = new HashMap<>(c.getCount()); while (c.moveToNext()) { String fpr = c.getString(1); if (fpr != null) list.put(c.getString(0), new TrustedFingerprint(fpr, c.getInt(2))); } c.close(); return list; } /** Converts a raw-string trusted fingerprint map to a {@link TrustedFingerprint} map. */ public static Map<String, TrustedFingerprint> fromTrustedFingerprintMap(Map<String, String> props) { Map<String, Keyring.TrustedFingerprint> keys = new HashMap<>(props.size()); for (Map.Entry e : props.entrySet()) { TrustedFingerprint fingerprint = TrustedFingerprint .fromString((String) e.getValue()); if (fingerprint != null) { keys.put((String) e.getKey(), fingerprint); } } return keys; } /** Converts a {@link TrustedFingerprint} map to a raw-string trusted fingerprint map. */ public static Map<String, String> toTrustedFingerprintMap(Map<String, TrustedFingerprint> props) { Map<String, String> keys = new HashMap<>(props.size()); for (Map.Entry<String, TrustedFingerprint> e : props.entrySet()) { TrustedFingerprint fingerprint = e.getValue(); if (fingerprint != null) { keys.put(e.getKey(), e.toString()); } } return keys; } public static final class TrustedFingerprint { public final String fingerprint; public final int trustLevel; TrustedFingerprint(String fingerprint, int trustLevel) { this.fingerprint = fingerprint; this.trustLevel = trustLevel; } @Override public String toString() { return fingerprint + "|" + trustLevel; } public static TrustedFingerprint fromString(String value) { if (!TextUtils.isEmpty(value)) { String[] parsed = value.split("\\|"); String fingerprint = parsed[0]; int trustLevel = MyUsers.Keys.TRUST_UNKNOWN; if (parsed.length > 1) { String _trustLevel = parsed[1]; try { trustLevel = Integer.parseInt(_trustLevel); } catch (NumberFormatException ignored) { } } return new TrustedFingerprint(fingerprint, trustLevel); } return null; } } public static final class TrustedPublicKeyData { public final byte[] keyData; public final int trustLevel; private TrustedPublicKeyData(byte[] keyData, int trustLevel) { this.keyData = keyData; this.trustLevel = trustLevel; } } }