mirror of
https://github.com/moparisthebest/open-keychain
synced 2025-02-25 16:01:52 -05:00
Merge pull request #553 from Valodim/split-masterKeyId
Split master key & partial fix for uid order
This commit is contained in:
commit
85bb3d9480
@ -51,7 +51,7 @@ public class ExportHelper {
|
|||||||
|
|
||||||
public void deleteKey(Uri dataUri, Handler deleteHandler) {
|
public void deleteKey(Uri dataUri, Handler deleteHandler) {
|
||||||
try {
|
try {
|
||||||
long masterKeyId = ProviderHelper.getMasterKeyId(mActivity, dataUri);
|
long masterKeyId = ProviderHelper.extractOrGetMasterKeyId(mActivity, dataUri);
|
||||||
|
|
||||||
// Create a new Messenger for the communication back
|
// Create a new Messenger for the communication back
|
||||||
Messenger messenger = new Messenger(deleteHandler);
|
Messenger messenger = new Messenger(deleteHandler);
|
||||||
|
@ -122,15 +122,20 @@ public class ProviderHelper {
|
|||||||
* Find the master key id related to a given query. The id will either be extracted from the
|
* Find the master key id related to a given query. The id will either be extracted from the
|
||||||
* query, which should work for all specific /key_rings/ queries, or will be queried if it can't.
|
* query, which should work for all specific /key_rings/ queries, or will be queried if it can't.
|
||||||
*/
|
*/
|
||||||
public static long getMasterKeyId(Context context, Uri queryUri) throws NotFoundException {
|
public static long extractOrGetMasterKeyId(Context context, Uri queryUri)
|
||||||
|
throws NotFoundException {
|
||||||
// try extracting from the uri first
|
// try extracting from the uri first
|
||||||
// String firstSegment = queryUri.getPathSegments().get(1);
|
String firstSegment = queryUri.getPathSegments().get(1);
|
||||||
// if(!firstSegment.equals("find")) try {
|
if(!firstSegment.equals("find")) try {
|
||||||
// return Long.parseLong(firstSegment);
|
return Long.parseLong(firstSegment);
|
||||||
// } catch(NumberFormatException e) {
|
} catch(NumberFormatException e) {
|
||||||
// // didn't work? oh well.
|
// didn't work? oh well.
|
||||||
// Log.d(Constants.TAG, "Couldn't get masterKeyId from URI, querying...");
|
Log.d(Constants.TAG, "Couldn't get masterKeyId from URI, querying...");
|
||||||
// }
|
}
|
||||||
|
return getMasterKeyId(context, queryUri);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static long getMasterKeyId(Context context, Uri queryUri) throws NotFoundException {
|
||||||
Object data = getGenericData(context, queryUri, KeyRings.MASTER_KEY_ID, FIELD_TYPE_INTEGER);
|
Object data = getGenericData(context, queryUri, KeyRings.MASTER_KEY_ID, FIELD_TYPE_INTEGER);
|
||||||
if(data != null) {
|
if(data != null) {
|
||||||
return (Long) data;
|
return (Long) data;
|
||||||
@ -461,70 +466,49 @@ public class ProviderHelper {
|
|||||||
return ContentProviderOperation.newInsert(uri).withValues(values).build();
|
return ContentProviderOperation.newInsert(uri).withValues(values).build();
|
||||||
}
|
}
|
||||||
|
|
||||||
public static ArrayList<String> getKeyRingsAsArmoredString(Context context, long[] masterKeyIds) {
|
private static String getKeyRingAsArmoredString(Context context, byte[] data) throws IOException {
|
||||||
ArrayList<String> output = new ArrayList<String>();
|
Object keyRing = null;
|
||||||
|
if (data != null) {
|
||||||
if (masterKeyIds != null && masterKeyIds.length > 0) {
|
keyRing = PgpConversionHelper.BytesToPGPKeyRing(data);
|
||||||
|
|
||||||
Cursor cursor = getCursorWithSelectedKeyringMasterKeyIds(context, masterKeyIds);
|
|
||||||
|
|
||||||
if (cursor != null) {
|
|
||||||
int masterIdCol = cursor.getColumnIndex(KeyRingData.MASTER_KEY_ID);
|
|
||||||
int dataCol = cursor.getColumnIndex(KeyRingData.KEY_RING_DATA);
|
|
||||||
if (cursor.moveToFirst()) {
|
|
||||||
do {
|
|
||||||
Log.d(Constants.TAG, "masterKeyId: " + cursor.getLong(masterIdCol));
|
|
||||||
|
|
||||||
// get actual keyring data blob and write it to ByteArrayOutputStream
|
|
||||||
try {
|
|
||||||
Object keyRing = null;
|
|
||||||
byte[] data = cursor.getBlob(dataCol);
|
|
||||||
if (data != null) {
|
|
||||||
keyRing = PgpConversionHelper.BytesToPGPKeyRing(data);
|
|
||||||
}
|
|
||||||
|
|
||||||
ByteArrayOutputStream bos = new ByteArrayOutputStream();
|
|
||||||
ArmoredOutputStream aos = new ArmoredOutputStream(bos);
|
|
||||||
aos.setHeader("Version", PgpHelper.getFullVersion(context));
|
|
||||||
|
|
||||||
if (keyRing instanceof PGPSecretKeyRing) {
|
|
||||||
aos.write(((PGPSecretKeyRing) keyRing).getEncoded());
|
|
||||||
} else if (keyRing instanceof PGPPublicKeyRing) {
|
|
||||||
aos.write(((PGPPublicKeyRing) keyRing).getEncoded());
|
|
||||||
}
|
|
||||||
aos.close();
|
|
||||||
|
|
||||||
String armoredKey = bos.toString("UTF-8");
|
|
||||||
|
|
||||||
Log.d(Constants.TAG, "armoredKey:" + armoredKey);
|
|
||||||
|
|
||||||
output.add(armoredKey);
|
|
||||||
} catch (IOException e) {
|
|
||||||
Log.e(Constants.TAG, "IOException", e);
|
|
||||||
}
|
|
||||||
} while (cursor.moveToNext());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (cursor != null) {
|
|
||||||
cursor.close();
|
|
||||||
}
|
|
||||||
|
|
||||||
} else {
|
|
||||||
Log.e(Constants.TAG, "No master keys given!");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (output.size() > 0) {
|
ByteArrayOutputStream bos = new ByteArrayOutputStream();
|
||||||
return output;
|
ArmoredOutputStream aos = new ArmoredOutputStream(bos);
|
||||||
} else {
|
aos.setHeader("Version", PgpHelper.getFullVersion(context));
|
||||||
return null;
|
|
||||||
|
if (keyRing instanceof PGPSecretKeyRing) {
|
||||||
|
aos.write(((PGPSecretKeyRing) keyRing).getEncoded());
|
||||||
|
} else if (keyRing instanceof PGPPublicKeyRing) {
|
||||||
|
aos.write(((PGPPublicKeyRing) keyRing).getEncoded());
|
||||||
}
|
}
|
||||||
|
aos.close();
|
||||||
|
|
||||||
|
String armoredKey = bos.toString("UTF-8");
|
||||||
|
|
||||||
|
Log.d(Constants.TAG, "armoredKey:" + armoredKey);
|
||||||
|
|
||||||
|
return armoredKey;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static Cursor getCursorWithSelectedKeyringMasterKeyIds(Context context, long[] masterKeyIds) {
|
public static String getKeyRingAsArmoredString(Context context, Uri uri)
|
||||||
Cursor cursor = null;
|
throws NotFoundException, IOException {
|
||||||
if (masterKeyIds != null && masterKeyIds.length > 0) {
|
byte[] data = (byte[]) ProviderHelper.getGenericData(
|
||||||
|
context, uri, KeyRingData.KEY_RING_DATA, ProviderHelper.FIELD_TYPE_BLOB);
|
||||||
|
return getKeyRingAsArmoredString(context, data);
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO This method is NOT ACTUALLY USED. Is this preparation for something, or just dead code?
|
||||||
|
public static ArrayList<String> getKeyRingsAsArmoredString(Context context, long[] masterKeyIds)
|
||||||
|
throws IOException {
|
||||||
|
ArrayList<String> output = new ArrayList<String>();
|
||||||
|
|
||||||
|
if (masterKeyIds == null || masterKeyIds.length == 0) {
|
||||||
|
Log.e(Constants.TAG, "No master keys given!");
|
||||||
|
return output;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Build a cursor for the selected masterKeyIds
|
||||||
|
Cursor cursor = null; {
|
||||||
String inMasterKeyList = KeyRingData.MASTER_KEY_ID + " IN (";
|
String inMasterKeyList = KeyRingData.MASTER_KEY_ID + " IN (";
|
||||||
for (int i = 0; i < masterKeyIds.length; ++i) {
|
for (int i = 0; i < masterKeyIds.length; ++i) {
|
||||||
if (i != 0) {
|
if (i != 0) {
|
||||||
@ -536,10 +520,37 @@ public class ProviderHelper {
|
|||||||
|
|
||||||
cursor = context.getContentResolver().query(KeyRingData.buildPublicKeyRingUri(), new String[] {
|
cursor = context.getContentResolver().query(KeyRingData.buildPublicKeyRingUri(), new String[] {
|
||||||
KeyRingData._ID, KeyRingData.MASTER_KEY_ID, KeyRingData.KEY_RING_DATA
|
KeyRingData._ID, KeyRingData.MASTER_KEY_ID, KeyRingData.KEY_RING_DATA
|
||||||
}, inMasterKeyList, null, null);
|
}, inMasterKeyList, null, null);
|
||||||
}
|
}
|
||||||
|
|
||||||
return cursor;
|
if (cursor != null) {
|
||||||
|
int masterIdCol = cursor.getColumnIndex(KeyRingData.MASTER_KEY_ID);
|
||||||
|
int dataCol = cursor.getColumnIndex(KeyRingData.KEY_RING_DATA);
|
||||||
|
if (cursor.moveToFirst()) {
|
||||||
|
do {
|
||||||
|
Log.d(Constants.TAG, "masterKeyId: " + cursor.getLong(masterIdCol));
|
||||||
|
|
||||||
|
byte[] data = cursor.getBlob(dataCol);
|
||||||
|
|
||||||
|
// get actual keyring data blob and write it to ByteArrayOutputStream
|
||||||
|
try {
|
||||||
|
output.add(getKeyRingAsArmoredString(context, data));
|
||||||
|
} catch (IOException e) {
|
||||||
|
Log.e(Constants.TAG, "IOException", e);
|
||||||
|
}
|
||||||
|
} while (cursor.moveToNext());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (cursor != null) {
|
||||||
|
cursor.close();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (output.size() > 0) {
|
||||||
|
return output;
|
||||||
|
} else {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public static ArrayList<String> getRegisteredApiApps(Context context) {
|
public static ArrayList<String> getRegisteredApiApps(Context context) {
|
||||||
|
@ -180,7 +180,8 @@ public class AccountSettingsFragment extends Fragment implements
|
|||||||
if (resultCode == Activity.RESULT_OK) {
|
if (resultCode == Activity.RESULT_OK) {
|
||||||
// select newly created key
|
// select newly created key
|
||||||
try {
|
try {
|
||||||
long masterKeyId = ProviderHelper.getMasterKeyId(getActivity(), data.getData());
|
long masterKeyId = ProviderHelper.extractOrGetMasterKeyId(
|
||||||
|
getActivity(), data.getData());
|
||||||
mSelectKeyFragment.selectKey(masterKeyId);
|
mSelectKeyFragment.selectKey(masterKeyId);
|
||||||
} catch (ProviderHelper.NotFoundException e) {
|
} catch (ProviderHelper.NotFoundException e) {
|
||||||
Log.e(Constants.TAG, "key not found!", e);
|
Log.e(Constants.TAG, "key not found!", e);
|
||||||
|
@ -200,6 +200,31 @@ public class PassphraseCacheService extends Service {
|
|||||||
return cachedPassphrase;
|
return cachedPassphrase;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static boolean hasPassphrase(PGPSecretKeyRing secretKeyRing) {
|
||||||
|
PGPSecretKey secretKey = null;
|
||||||
|
boolean foundValidKey = false;
|
||||||
|
for (Iterator keys = secretKeyRing.getSecretKeys(); keys.hasNext(); ) {
|
||||||
|
secretKey = (PGPSecretKey) keys.next();
|
||||||
|
if (!secretKey.isPrivateKeyEmpty()) {
|
||||||
|
foundValidKey = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if(!foundValidKey) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
PBESecretKeyDecryptor keyDecryptor = new JcePBESecretKeyDecryptorBuilder()
|
||||||
|
.setProvider("SC").build("".toCharArray());
|
||||||
|
PGPPrivateKey testKey = secretKey.extractPrivateKey(keyDecryptor);
|
||||||
|
return testKey == null;
|
||||||
|
} catch(PGPException e) {
|
||||||
|
// this means the crc check failed -> passphrase required
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Checks if key has a passphrase.
|
* Checks if key has a passphrase.
|
||||||
*
|
*
|
||||||
@ -210,27 +235,7 @@ public class PassphraseCacheService extends Service {
|
|||||||
// check if the key has no passphrase
|
// check if the key has no passphrase
|
||||||
try {
|
try {
|
||||||
PGPSecretKeyRing secRing = ProviderHelper.getPGPSecretKeyRing(context, secretKeyId);
|
PGPSecretKeyRing secRing = ProviderHelper.getPGPSecretKeyRing(context, secretKeyId);
|
||||||
PGPSecretKey secretKey = null;
|
return hasPassphrase(secRing);
|
||||||
boolean foundValidKey = false;
|
|
||||||
for (Iterator keys = secRing.getSecretKeys(); keys.hasNext(); ) {
|
|
||||||
secretKey = (PGPSecretKey) keys.next();
|
|
||||||
if (!secretKey.isPrivateKeyEmpty()) {
|
|
||||||
foundValidKey = true;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!foundValidKey) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
PBESecretKeyDecryptor keyDecryptor = new JcePBESecretKeyDecryptorBuilder().setProvider(
|
|
||||||
"SC").build("".toCharArray());
|
|
||||||
PGPPrivateKey testKey = secretKey.extractPrivateKey(keyDecryptor);
|
|
||||||
if (testKey != null) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
} catch (PGPException e) {
|
|
||||||
// silently catch
|
|
||||||
} catch (ProviderHelper.NotFoundException e) {
|
} catch (ProviderHelper.NotFoundException e) {
|
||||||
Log.e(Constants.TAG, "key not found!", e);
|
Log.e(Constants.TAG, "key not found!", e);
|
||||||
}
|
}
|
||||||
|
@ -39,10 +39,12 @@ import android.widget.CheckBox;
|
|||||||
import android.widget.CompoundButton;
|
import android.widget.CompoundButton;
|
||||||
import android.widget.CompoundButton.OnCheckedChangeListener;
|
import android.widget.CompoundButton.OnCheckedChangeListener;
|
||||||
import android.widget.LinearLayout;
|
import android.widget.LinearLayout;
|
||||||
|
import android.widget.Toast;
|
||||||
|
|
||||||
import com.beardedhen.androidbootstrap.BootstrapButton;
|
import com.beardedhen.androidbootstrap.BootstrapButton;
|
||||||
import com.devspark.appmsg.AppMsg;
|
import com.devspark.appmsg.AppMsg;
|
||||||
|
|
||||||
|
import org.spongycastle.openpgp.PGPException;
|
||||||
import org.spongycastle.openpgp.PGPSecretKey;
|
import org.spongycastle.openpgp.PGPSecretKey;
|
||||||
import org.spongycastle.openpgp.PGPSecretKeyRing;
|
import org.spongycastle.openpgp.PGPSecretKeyRing;
|
||||||
import org.sufficientlysecure.keychain.Constants;
|
import org.sufficientlysecure.keychain.Constants;
|
||||||
@ -287,34 +289,16 @@ public class EditKeyActivity extends ActionBarActivity implements EditorListener
|
|||||||
Log.d(Constants.TAG, "uri: " + mDataUri);
|
Log.d(Constants.TAG, "uri: " + mDataUri);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
// get master key id using row id
|
Uri secretUri = KeychainContract.KeyRingData.buildSecretKeyRingUri(mDataUri);
|
||||||
long masterKeyId = ProviderHelper.getMasterKeyId(this, mDataUri);
|
mKeyRing = (PGPSecretKeyRing) ProviderHelper.getPGPKeyRing(this, secretUri);
|
||||||
finallyEdit(masterKeyId);
|
|
||||||
} catch (ProviderHelper.NotFoundException e) {
|
|
||||||
Log.e(Constants.TAG, "key not found!", e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@SuppressWarnings("unchecked")
|
PGPSecretKey masterKey = mKeyRing.getSecretKey();
|
||||||
private void finallyEdit(final long masterKeyId) {
|
|
||||||
if (masterKeyId != 0) {
|
|
||||||
PGPSecretKey masterKey = null;
|
|
||||||
try {
|
|
||||||
mKeyRing = ProviderHelper.getPGPSecretKeyRing(this, masterKeyId);
|
|
||||||
|
|
||||||
masterKey = mKeyRing.getSecretKey();
|
|
||||||
mMasterCanSign = PgpKeyHelper.isCertificationKey(mKeyRing.getSecretKey());
|
mMasterCanSign = PgpKeyHelper.isCertificationKey(mKeyRing.getSecretKey());
|
||||||
for (PGPSecretKey key : new IterableIterator<PGPSecretKey>(mKeyRing.getSecretKeys())) {
|
for (PGPSecretKey key : new IterableIterator<PGPSecretKey>(mKeyRing.getSecretKeys())) {
|
||||||
mKeys.add(key);
|
mKeys.add(key);
|
||||||
mKeysUsages.add(-1); // get usage when view is created
|
mKeysUsages.add(-1); // get usage when view is created
|
||||||
}
|
}
|
||||||
} catch (ProviderHelper.NotFoundException e) {
|
|
||||||
Log.e(Constants.TAG, "Keyring not found with masterKeyId: " + masterKeyId);
|
|
||||||
AppMsg.makeText(this, R.string.error_no_secret_key_found, AppMsg.STYLE_ALERT).show();
|
|
||||||
// TODO
|
|
||||||
}
|
|
||||||
if (masterKey != null) {
|
|
||||||
boolean isSet = false;
|
boolean isSet = false;
|
||||||
for (String userId : new IterableIterator<String>(masterKey.getUserIDs())) {
|
for (String userId : new IterableIterator<String>(masterKey.getUserIDs())) {
|
||||||
Log.d(Constants.TAG, "Added userId " + userId);
|
Log.d(Constants.TAG, "Added userId " + userId);
|
||||||
@ -327,17 +311,23 @@ public class EditKeyActivity extends ActionBarActivity implements EditorListener
|
|||||||
}
|
}
|
||||||
mUserIds.add(userId);
|
mUserIds.add(userId);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
buildLayout(false);
|
||||||
|
|
||||||
|
mCurrentPassphrase = "";
|
||||||
|
mIsPassphraseSet = PassphraseCacheService.hasPassphrase(mKeyRing);
|
||||||
|
if (!mIsPassphraseSet) {
|
||||||
|
// check "no passphrase" checkbox and remove button
|
||||||
|
mNoPassphrase.setChecked(true);
|
||||||
|
mChangePassphrase.setVisibility(View.GONE);
|
||||||
|
}
|
||||||
|
|
||||||
|
} catch (ProviderHelper.NotFoundException e) {
|
||||||
|
Log.e(Constants.TAG, "Keyring not found: " + e.getMessage(), e);
|
||||||
|
Toast.makeText(this, R.string.error_no_secret_key_found, Toast.LENGTH_SHORT).show();
|
||||||
|
finish();
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
mCurrentPassphrase = "";
|
|
||||||
buildLayout(false);
|
|
||||||
|
|
||||||
mIsPassphraseSet = PassphraseCacheService.hasPassphrase(this, masterKeyId);
|
|
||||||
if (!mIsPassphraseSet) {
|
|
||||||
// check "no passphrase" checkbox and remove button
|
|
||||||
mNoPassphrase.setChecked(true);
|
|
||||||
mChangePassphrase.setVisibility(View.GONE);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -165,10 +165,11 @@ public class EncryptAsymmetricFragment extends Fragment {
|
|||||||
if (preselectedEncryptionKeyIds != null) {
|
if (preselectedEncryptionKeyIds != null) {
|
||||||
Vector<Long> goodIds = new Vector<Long>();
|
Vector<Long> goodIds = new Vector<Long>();
|
||||||
for (int i = 0; i < preselectedEncryptionKeyIds.length; ++i) {
|
for (int i = 0; i < preselectedEncryptionKeyIds.length; ++i) {
|
||||||
// TODO check for available encrypt keys... is this even relevant?
|
// TODO One query per selected key?! wtf
|
||||||
try {
|
try {
|
||||||
long id = ProviderHelper.getMasterKeyId(getActivity(),
|
long id = ProviderHelper.getMasterKeyId(getActivity(),
|
||||||
KeyRings.buildUnifiedKeyRingsFindBySubkeyUri(Long.toString(preselectedEncryptionKeyIds[i]))
|
KeyRings.buildUnifiedKeyRingsFindBySubkeyUri(
|
||||||
|
Long.toString(preselectedEncryptionKeyIds[i]))
|
||||||
);
|
);
|
||||||
goodIds.add(id);
|
goodIds.add(id);
|
||||||
} catch (ProviderHelper.NotFoundException e) {
|
} catch (ProviderHelper.NotFoundException e) {
|
||||||
|
@ -219,12 +219,8 @@ public class ViewKeyActivity extends ActionBarActivity {
|
|||||||
} else {
|
} else {
|
||||||
// get public keyring as ascii armored string
|
// get public keyring as ascii armored string
|
||||||
try {
|
try {
|
||||||
long masterKeyId = ProviderHelper.getMasterKeyId(this, dataUri);
|
Uri uri = KeychainContract.KeyRingData.buildPublicKeyRingUri(dataUri);
|
||||||
|
content = ProviderHelper.getKeyRingAsArmoredString(this, uri);
|
||||||
ArrayList<String> keyringArmored = ProviderHelper.getKeyRingsAsArmoredString(
|
|
||||||
this, new long[]{masterKeyId});
|
|
||||||
|
|
||||||
content = keyringArmored.get(0);
|
|
||||||
|
|
||||||
// Android will fail with android.os.TransactionTooLargeException if key is too big
|
// Android will fail with android.os.TransactionTooLargeException if key is too big
|
||||||
// see http://www.lonestarprod.com/?p=34
|
// see http://www.lonestarprod.com/?p=34
|
||||||
@ -233,8 +229,12 @@ public class ViewKeyActivity extends ActionBarActivity {
|
|||||||
AppMsg.STYLE_ALERT).show();
|
AppMsg.STYLE_ALERT).show();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
} catch (IOException e) {
|
||||||
|
Log.e(Constants.TAG, "error processing key!", e);
|
||||||
|
AppMsg.makeText(this, R.string.error_invalid_data, AppMsg.STYLE_ALERT).show();
|
||||||
} catch (ProviderHelper.NotFoundException e) {
|
} catch (ProviderHelper.NotFoundException e) {
|
||||||
Log.e(Constants.TAG, "key not found!", e);
|
Log.e(Constants.TAG, "key not found!", e);
|
||||||
|
AppMsg.makeText(this, R.string.error_key_not_found, AppMsg.STYLE_ALERT).show();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -259,16 +259,18 @@ public class ViewKeyActivity extends ActionBarActivity {
|
|||||||
private void copyToClipboard(Uri dataUri) {
|
private void copyToClipboard(Uri dataUri) {
|
||||||
// get public keyring as ascii armored string
|
// get public keyring as ascii armored string
|
||||||
try {
|
try {
|
||||||
long masterKeyId = ProviderHelper.getMasterKeyId(this, dataUri);
|
Uri uri = KeychainContract.KeyRingData.buildPublicKeyRingUri(dataUri);
|
||||||
|
String keyringArmored = ProviderHelper.getKeyRingAsArmoredString(this, uri);
|
||||||
|
|
||||||
ArrayList<String> keyringArmored = ProviderHelper.getKeyRingsAsArmoredString(
|
ClipboardReflection.copyToClipboard(this, keyringArmored);
|
||||||
this, new long[]{masterKeyId});
|
|
||||||
|
|
||||||
ClipboardReflection.copyToClipboard(this, keyringArmored.get(0));
|
|
||||||
AppMsg.makeText(this, R.string.key_copied_to_clipboard, AppMsg.STYLE_INFO)
|
AppMsg.makeText(this, R.string.key_copied_to_clipboard, AppMsg.STYLE_INFO)
|
||||||
.show();
|
.show();
|
||||||
|
} catch (IOException e) {
|
||||||
|
Log.e(Constants.TAG, "error processing key!", e);
|
||||||
|
AppMsg.makeText(this, R.string.error_key_processing, AppMsg.STYLE_ALERT).show();
|
||||||
} catch (ProviderHelper.NotFoundException e) {
|
} catch (ProviderHelper.NotFoundException e) {
|
||||||
Log.e(Constants.TAG, "key not found!", e);
|
Log.e(Constants.TAG, "key not found!", e);
|
||||||
|
AppMsg.makeText(this, R.string.error_key_not_found, AppMsg.STYLE_ALERT).show();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -331,11 +331,8 @@ public class ViewKeyMainFragment extends Fragment implements
|
|||||||
}
|
}
|
||||||
|
|
||||||
private void encryptToContact(Uri dataUri) {
|
private void encryptToContact(Uri dataUri) {
|
||||||
// TODO preselect from uri? should be feasible without trivial query
|
|
||||||
try {
|
try {
|
||||||
long keyId = ProviderHelper.getMasterKeyId(getActivity(),
|
long keyId = ProviderHelper.extractOrGetMasterKeyId(getActivity(), dataUri);
|
||||||
KeyRingData.buildPublicKeyRingUri(dataUri));
|
|
||||||
|
|
||||||
long[] encryptionKeyIds = new long[]{ keyId };
|
long[] encryptionKeyIds = new long[]{ keyId };
|
||||||
Intent intent = new Intent(getActivity(), EncryptActivity.class);
|
Intent intent = new Intent(getActivity(), EncryptActivity.class);
|
||||||
intent.setAction(EncryptActivity.ACTION_ENCRYPT);
|
intent.setAction(EncryptActivity.ACTION_ENCRYPT);
|
||||||
|
@ -21,9 +21,12 @@ import android.os.Parcel;
|
|||||||
import android.os.Parcelable;
|
import android.os.Parcelable;
|
||||||
import android.util.SparseArray;
|
import android.util.SparseArray;
|
||||||
|
|
||||||
|
import org.spongycastle.bcpg.SignatureSubpacketTags;
|
||||||
import org.spongycastle.openpgp.PGPKeyRing;
|
import org.spongycastle.openpgp.PGPKeyRing;
|
||||||
import org.spongycastle.openpgp.PGPPublicKey;
|
import org.spongycastle.openpgp.PGPPublicKey;
|
||||||
import org.spongycastle.openpgp.PGPSecretKeyRing;
|
import org.spongycastle.openpgp.PGPSecretKeyRing;
|
||||||
|
import org.spongycastle.openpgp.PGPSignature;
|
||||||
|
import org.spongycastle.openpgp.operator.jcajce.JcaPGPContentVerifierBuilderProvider;
|
||||||
import org.sufficientlysecure.keychain.Constants;
|
import org.sufficientlysecure.keychain.Constants;
|
||||||
import org.sufficientlysecure.keychain.pgp.PgpKeyHelper;
|
import org.sufficientlysecure.keychain.pgp.PgpKeyHelper;
|
||||||
import org.sufficientlysecure.keychain.util.IterableIterator;
|
import org.sufficientlysecure.keychain.util.IterableIterator;
|
||||||
@ -46,31 +49,19 @@ public class ImportKeysListEntry implements Serializable, Parcelable {
|
|||||||
public int bitStrength;
|
public int bitStrength;
|
||||||
public String algorithm;
|
public String algorithm;
|
||||||
public boolean secretKey;
|
public boolean secretKey;
|
||||||
|
public String mPrimaryUserId;
|
||||||
|
|
||||||
private boolean mSelected;
|
private boolean mSelected;
|
||||||
|
|
||||||
private byte[] mBytes = new byte[]{};
|
private byte[] mBytes = new byte[]{};
|
||||||
|
|
||||||
public ImportKeysListEntry(ImportKeysListEntry b) {
|
|
||||||
this.userIds = b.userIds;
|
|
||||||
this.keyId = b.keyId;
|
|
||||||
this.revoked = b.revoked;
|
|
||||||
this.date = b.date;
|
|
||||||
this.fingerPrintHex = b.fingerPrintHex;
|
|
||||||
this.keyIdHex = b.keyIdHex;
|
|
||||||
this.bitStrength = b.bitStrength;
|
|
||||||
this.algorithm = b.algorithm;
|
|
||||||
this.secretKey = b.secretKey;
|
|
||||||
this.mSelected = b.mSelected;
|
|
||||||
this.mBytes = b.mBytes;
|
|
||||||
}
|
|
||||||
|
|
||||||
public int describeContents() {
|
public int describeContents() {
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void writeToParcel(Parcel dest, int flags) {
|
public void writeToParcel(Parcel dest, int flags) {
|
||||||
|
dest.writeString(mPrimaryUserId);
|
||||||
dest.writeStringList(userIds);
|
dest.writeStringList(userIds);
|
||||||
dest.writeLong(keyId);
|
dest.writeLong(keyId);
|
||||||
dest.writeByte((byte) (revoked ? 1 : 0));
|
dest.writeByte((byte) (revoked ? 1 : 0));
|
||||||
@ -88,6 +79,7 @@ public class ImportKeysListEntry implements Serializable, Parcelable {
|
|||||||
public static final Creator<ImportKeysListEntry> CREATOR = new Creator<ImportKeysListEntry>() {
|
public static final Creator<ImportKeysListEntry> CREATOR = new Creator<ImportKeysListEntry>() {
|
||||||
public ImportKeysListEntry createFromParcel(final Parcel source) {
|
public ImportKeysListEntry createFromParcel(final Parcel source) {
|
||||||
ImportKeysListEntry vr = new ImportKeysListEntry();
|
ImportKeysListEntry vr = new ImportKeysListEntry();
|
||||||
|
vr.mPrimaryUserId = source.readString();
|
||||||
vr.userIds = new ArrayList<String>();
|
vr.userIds = new ArrayList<String>();
|
||||||
source.readStringList(vr.userIds);
|
source.readStringList(vr.userIds);
|
||||||
vr.keyId = source.readLong();
|
vr.keyId = source.readLong();
|
||||||
@ -198,6 +190,14 @@ public class ImportKeysListEntry implements Serializable, Parcelable {
|
|||||||
this.userIds = userIds;
|
this.userIds = userIds;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public String getPrimaryUserId() {
|
||||||
|
return mPrimaryUserId;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setPrimaryUserId(String uid) {
|
||||||
|
mPrimaryUserId = uid;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Constructor for later querying from keyserver
|
* Constructor for later querying from keyserver
|
||||||
*/
|
*/
|
||||||
@ -229,20 +229,39 @@ public class ImportKeysListEntry implements Serializable, Parcelable {
|
|||||||
} else {
|
} else {
|
||||||
secretKey = false;
|
secretKey = false;
|
||||||
}
|
}
|
||||||
|
PGPPublicKey key = pgpKeyRing.getPublicKey();
|
||||||
|
|
||||||
userIds = new ArrayList<String>();
|
userIds = new ArrayList<String>();
|
||||||
for (String userId : new IterableIterator<String>(pgpKeyRing.getPublicKey().getUserIDs())) {
|
for (String userId : new IterableIterator<String>(key.getUserIDs())) {
|
||||||
userIds.add(userId);
|
userIds.add(userId);
|
||||||
|
for(PGPSignature sig : new IterableIterator<PGPSignature>(key.getSignaturesForID(userId))) {
|
||||||
|
if(sig.getHashedSubPackets().hasSubpacket(SignatureSubpacketTags.PRIMARY_USER_ID)) {
|
||||||
|
try {
|
||||||
|
// make sure it's actually valid
|
||||||
|
sig.init(new JcaPGPContentVerifierBuilderProvider().setProvider(
|
||||||
|
Constants.BOUNCY_CASTLE_PROVIDER_NAME), key);
|
||||||
|
if (sig.verifyCertification(userId, key)) {
|
||||||
|
mPrimaryUserId = userId;
|
||||||
|
}
|
||||||
|
} catch(Exception e) {
|
||||||
|
// nothing bad happens, the key is just not considered the primary key id
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// if there was no user id flagged as primary, use the first one
|
||||||
|
if(mPrimaryUserId == null) {
|
||||||
|
mPrimaryUserId = userIds.get(0);
|
||||||
}
|
}
|
||||||
|
|
||||||
this.keyId = pgpKeyRing.getPublicKey().getKeyID();
|
this.keyId = key.getKeyID();
|
||||||
this.keyIdHex = PgpKeyHelper.convertKeyIdToHex(keyId);
|
this.keyIdHex = PgpKeyHelper.convertKeyIdToHex(keyId);
|
||||||
|
|
||||||
this.revoked = pgpKeyRing.getPublicKey().isRevoked();
|
this.revoked = key.isRevoked();
|
||||||
this.fingerPrintHex = PgpKeyHelper.convertFingerprintToHex(pgpKeyRing.getPublicKey()
|
this.fingerPrintHex = PgpKeyHelper.convertFingerprintToHex(key.getFingerprint());
|
||||||
.getFingerprint());
|
this.bitStrength = key.getBitStrength();
|
||||||
this.bitStrength = pgpKeyRing.getPublicKey().getBitStrength();
|
final int algorithm = key.getAlgorithm();
|
||||||
final int algorithm = pgpKeyRing.getPublicKey().getAlgorithm();
|
|
||||||
this.algorithm = getAlgorithmFromId(algorithm);
|
this.algorithm = getAlgorithmFromId(algorithm);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -28,14 +28,19 @@ import android.view.View;
|
|||||||
import android.widget.Button;
|
import android.widget.Button;
|
||||||
import android.widget.ImageView;
|
import android.widget.ImageView;
|
||||||
import android.widget.TextView;
|
import android.widget.TextView;
|
||||||
|
|
||||||
|
import com.devspark.appmsg.AppMsg;
|
||||||
|
|
||||||
import org.sufficientlysecure.keychain.Constants;
|
import org.sufficientlysecure.keychain.Constants;
|
||||||
import org.sufficientlysecure.keychain.R;
|
import org.sufficientlysecure.keychain.R;
|
||||||
import org.sufficientlysecure.keychain.pgp.PgpKeyHelper;
|
import org.sufficientlysecure.keychain.pgp.PgpKeyHelper;
|
||||||
|
import org.sufficientlysecure.keychain.provider.KeychainContract;
|
||||||
import org.sufficientlysecure.keychain.provider.KeychainContract.KeyRings;
|
import org.sufficientlysecure.keychain.provider.KeychainContract.KeyRings;
|
||||||
import org.sufficientlysecure.keychain.provider.ProviderHelper;
|
import org.sufficientlysecure.keychain.provider.ProviderHelper;
|
||||||
import org.sufficientlysecure.keychain.util.Log;
|
import org.sufficientlysecure.keychain.util.Log;
|
||||||
import org.sufficientlysecure.keychain.util.QrCodeUtils;
|
import org.sufficientlysecure.keychain.util.QrCodeUtils;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
|
|
||||||
public class ShareQrCodeDialogFragment extends DialogFragment {
|
public class ShareQrCodeDialogFragment extends DialogFragment {
|
||||||
@ -77,7 +82,6 @@ public class ShareQrCodeDialogFragment extends DialogFragment {
|
|||||||
mFingerprintOnly = getArguments().getBoolean(ARG_FINGERPRINT_ONLY);
|
mFingerprintOnly = getArguments().getBoolean(ARG_FINGERPRINT_ONLY);
|
||||||
|
|
||||||
AlertDialog.Builder alert = new AlertDialog.Builder(getActivity());
|
AlertDialog.Builder alert = new AlertDialog.Builder(getActivity());
|
||||||
|
|
||||||
alert.setTitle(R.string.share_qr_code_dialog_title);
|
alert.setTitle(R.string.share_qr_code_dialog_title);
|
||||||
|
|
||||||
LayoutInflater inflater = activity.getLayoutInflater();
|
LayoutInflater inflater = activity.getLayoutInflater();
|
||||||
@ -95,7 +99,8 @@ public class ShareQrCodeDialogFragment extends DialogFragment {
|
|||||||
getActivity(), KeyRings.buildUnifiedKeyRingUri(dataUri),
|
getActivity(), KeyRings.buildUnifiedKeyRingUri(dataUri),
|
||||||
KeyRings.FINGERPRINT, ProviderHelper.FIELD_TYPE_BLOB);
|
KeyRings.FINGERPRINT, ProviderHelper.FIELD_TYPE_BLOB);
|
||||||
if(blob == null) {
|
if(blob == null) {
|
||||||
// TODO error handling?!
|
Log.e(Constants.TAG, "key not found!");
|
||||||
|
AppMsg.makeText(getActivity(), R.string.error_key_not_found, AppMsg.STYLE_ALERT).show();
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -106,20 +111,18 @@ public class ShareQrCodeDialogFragment extends DialogFragment {
|
|||||||
} else {
|
} else {
|
||||||
mText.setText(R.string.share_qr_code_dialog_start);
|
mText.setText(R.string.share_qr_code_dialog_start);
|
||||||
|
|
||||||
// TODO works, but
|
|
||||||
long masterKeyId = 0;
|
|
||||||
try {
|
try {
|
||||||
masterKeyId = ProviderHelper.getMasterKeyId(getActivity(), dataUri);
|
Uri uri = KeychainContract.KeyRingData.buildPublicKeyRingUri(dataUri);
|
||||||
|
content = ProviderHelper.getKeyRingAsArmoredString(getActivity(), uri);
|
||||||
|
} catch (IOException e) {
|
||||||
|
Log.e(Constants.TAG, "error processing key!", e);
|
||||||
|
AppMsg.makeText(getActivity(), R.string.error_invalid_data, AppMsg.STYLE_ALERT).show();
|
||||||
|
return null;
|
||||||
} catch (ProviderHelper.NotFoundException e) {
|
} catch (ProviderHelper.NotFoundException e) {
|
||||||
Log.e(Constants.TAG, "key not found!", e);
|
Log.e(Constants.TAG, "key not found!", e);
|
||||||
|
AppMsg.makeText(getActivity(), R.string.error_key_not_found, AppMsg.STYLE_ALERT).show();
|
||||||
|
return null;
|
||||||
}
|
}
|
||||||
// get public keyring as ascii armored string
|
|
||||||
ArrayList<String> keyringArmored = ProviderHelper.getKeyRingsAsArmoredString(
|
|
||||||
getActivity(), new long[] { masterKeyId });
|
|
||||||
|
|
||||||
// TODO: binary?
|
|
||||||
|
|
||||||
content = keyringArmored.get(0);
|
|
||||||
|
|
||||||
// OnClickListener are set in onResume to prevent automatic dismissing of Dialogs
|
// OnClickListener are set in onResume to prevent automatic dismissing of Dialogs
|
||||||
// http://bit.ly/O5vfaR
|
// http://bit.ly/O5vfaR
|
||||||
|
@ -294,6 +294,7 @@ public class HkpKeyServer extends KeyServer {
|
|||||||
userIds.add(tmp);
|
userIds.add(tmp);
|
||||||
}
|
}
|
||||||
entry.setUserIds(userIds);
|
entry.setUserIds(userIds);
|
||||||
|
entry.setPrimaryUserId(userIds.get(0));
|
||||||
|
|
||||||
results.add(entry);
|
results.add(entry);
|
||||||
}
|
}
|
||||||
|
@ -1,21 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
|
||||||
<FrameLayout
|
|
||||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
|
||||||
android:orientation="horizontal"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="match_parent">
|
|
||||||
|
|
||||||
<android.support.v4.widget.DrawerLayout
|
|
||||||
xmlns:bootstrapbutton="http://schemas.android.com/apk/res-auto"
|
|
||||||
android:id="@+id/drawer_layout"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="match_parent">
|
|
||||||
|
|
||||||
|
|
||||||
<include layout="@layout/drawer_list"/>
|
|
||||||
|
|
||||||
</android.support.v4.widget.DrawerLayout>
|
|
||||||
|
|
||||||
<include layout="@layout/import_keys_content"/>
|
|
||||||
|
|
||||||
</FrameLayout>
|
|
@ -1,11 +1,50 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<android.support.v4.widget.DrawerLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
android:id="@+id/drawer_layout"
|
xmlns:bootstrapbutton="http://schemas.android.com/apk/res-auto"
|
||||||
android:layout_width="match_parent"
|
android:id="@+id/content_frame"
|
||||||
android:layout_height="match_parent" >
|
android:layout_marginLeft="@dimen/drawer_content_padding"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_centerHorizontal="true">
|
||||||
|
|
||||||
<include layout="@layout/import_keys_content"/>
|
<FrameLayout
|
||||||
|
android:id="@+id/import_navigation_fragment"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_alignParentTop="true"
|
||||||
|
android:orientation="vertical"
|
||||||
|
android:paddingLeft="4dp"
|
||||||
|
android:paddingRight="4dp" />
|
||||||
|
|
||||||
<include layout="@layout/drawer_list" />
|
<LinearLayout
|
||||||
|
android:id="@+id/import_footer"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_alignParentBottom="true"
|
||||||
|
android:orientation="vertical"
|
||||||
|
android:paddingLeft="16dp"
|
||||||
|
android:paddingRight="16dp">
|
||||||
|
|
||||||
</android.support.v4.widget.DrawerLayout>
|
<com.beardedhen.androidbootstrap.BootstrapButton
|
||||||
|
android:id="@+id/import_import"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="50dp"
|
||||||
|
android:layout_marginTop="4dp"
|
||||||
|
android:layout_marginBottom="4dp"
|
||||||
|
android:text="@string/import_import"
|
||||||
|
bootstrapbutton:bb_icon_left="fa-download"
|
||||||
|
bootstrapbutton:bb_type="info" />
|
||||||
|
|
||||||
|
</LinearLayout>
|
||||||
|
|
||||||
|
<FrameLayout
|
||||||
|
android:id="@+id/import_keys_list_container"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent"
|
||||||
|
android:layout_above="@+id/import_footer"
|
||||||
|
android:layout_alignParentLeft="true"
|
||||||
|
android:layout_below="@+id/import_navigation_fragment"
|
||||||
|
android:orientation="vertical"
|
||||||
|
android:paddingLeft="4dp"
|
||||||
|
android:paddingRight="4dp" />
|
||||||
|
</RelativeLayout>
|
@ -1,50 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
|
||||||
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
|
||||||
xmlns:bootstrapbutton="http://schemas.android.com/apk/res-auto"
|
|
||||||
android:id="@+id/content_frame"
|
|
||||||
android:layout_marginLeft="@dimen/drawer_content_padding"
|
|
||||||
android:layout_width="wrap_content"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:layout_centerHorizontal="true">
|
|
||||||
|
|
||||||
<FrameLayout
|
|
||||||
android:id="@+id/import_navigation_fragment"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:layout_alignParentTop="true"
|
|
||||||
android:orientation="vertical"
|
|
||||||
android:paddingLeft="4dp"
|
|
||||||
android:paddingRight="4dp" />
|
|
||||||
|
|
||||||
<LinearLayout
|
|
||||||
android:id="@+id/import_footer"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:layout_alignParentBottom="true"
|
|
||||||
android:orientation="vertical"
|
|
||||||
android:paddingLeft="16dp"
|
|
||||||
android:paddingRight="16dp">
|
|
||||||
|
|
||||||
<com.beardedhen.androidbootstrap.BootstrapButton
|
|
||||||
android:id="@+id/import_import"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="50dp"
|
|
||||||
android:layout_marginTop="4dp"
|
|
||||||
android:layout_marginBottom="4dp"
|
|
||||||
android:text="@string/import_import"
|
|
||||||
bootstrapbutton:bb_icon_left="fa-download"
|
|
||||||
bootstrapbutton:bb_type="info" />
|
|
||||||
|
|
||||||
</LinearLayout>
|
|
||||||
|
|
||||||
<FrameLayout
|
|
||||||
android:id="@+id/import_keys_list_container"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="match_parent"
|
|
||||||
android:layout_above="@+id/import_footer"
|
|
||||||
android:layout_alignParentLeft="true"
|
|
||||||
android:layout_below="@+id/import_navigation_fragment"
|
|
||||||
android:orientation="vertical"
|
|
||||||
android:paddingLeft="4dp"
|
|
||||||
android:paddingRight="4dp" />
|
|
||||||
</RelativeLayout>
|
|
@ -525,5 +525,7 @@
|
|||||||
<string name="label_cert_type">Type</string>
|
<string name="label_cert_type">Type</string>
|
||||||
<string name="can_certify">can certify</string>
|
<string name="can_certify">can certify</string>
|
||||||
<string name="can_certify_not">cannot certify</string>
|
<string name="can_certify_not">cannot certify</string>
|
||||||
|
<string name="error_key_not_found">Key not found!</string>
|
||||||
|
<string name="error_key_processing">Error processing key!</string>
|
||||||
|
|
||||||
</resources>
|
</resources>
|
||||||
|
Loading…
x
Reference in New Issue
Block a user