yubikey certifications!

This commit is contained in:
Vincent Breitmoser 2015-03-18 21:12:31 +01:00
parent aca54e31ea
commit d46fc3740b
15 changed files with 476 additions and 241 deletions

View File

@ -30,6 +30,8 @@ import org.sufficientlysecure.keychain.pgp.CanonicalizedPublicKeyRing;
import org.sufficientlysecure.keychain.pgp.CanonicalizedSecretKey; import org.sufficientlysecure.keychain.pgp.CanonicalizedSecretKey;
import org.sufficientlysecure.keychain.pgp.CanonicalizedSecretKey.SecretKeyType; import org.sufficientlysecure.keychain.pgp.CanonicalizedSecretKey.SecretKeyType;
import org.sufficientlysecure.keychain.pgp.CanonicalizedSecretKeyRing; import org.sufficientlysecure.keychain.pgp.CanonicalizedSecretKeyRing;
import org.sufficientlysecure.keychain.pgp.PgpCertifyOperation;
import org.sufficientlysecure.keychain.pgp.PgpCertifyOperation.PgpCertifyResult;
import org.sufficientlysecure.keychain.pgp.Progressable; import org.sufficientlysecure.keychain.pgp.Progressable;
import org.sufficientlysecure.keychain.pgp.UncachedKeyRing; import org.sufficientlysecure.keychain.pgp.UncachedKeyRing;
import org.sufficientlysecure.keychain.pgp.exception.PgpGeneralException; import org.sufficientlysecure.keychain.pgp.exception.PgpGeneralException;
@ -38,6 +40,8 @@ import org.sufficientlysecure.keychain.provider.ProviderHelper.NotFoundException
import org.sufficientlysecure.keychain.service.CertifyActionsParcel; import org.sufficientlysecure.keychain.service.CertifyActionsParcel;
import org.sufficientlysecure.keychain.service.CertifyActionsParcel.CertifyAction; import org.sufficientlysecure.keychain.service.CertifyActionsParcel.CertifyAction;
import org.sufficientlysecure.keychain.service.ContactSyncAdapterService; import org.sufficientlysecure.keychain.service.ContactSyncAdapterService;
import org.sufficientlysecure.keychain.service.input.NfcOperationsParcel;
import org.sufficientlysecure.keychain.service.input.NfcOperationsParcel.NfcSignOperationsBuilder;
import org.sufficientlysecure.keychain.ui.util.KeyFormattingUtils; import org.sufficientlysecure.keychain.ui.util.KeyFormattingUtils;
import org.sufficientlysecure.keychain.util.Log; import org.sufficientlysecure.keychain.util.Log;
@ -73,10 +77,6 @@ public class CertifyOperation extends BaseOperation {
mProviderHelper.getCanonicalizedSecretKeyRing(parcel.mMasterKeyId); mProviderHelper.getCanonicalizedSecretKeyRing(parcel.mMasterKeyId);
log.add(LogType.MSG_CRT_UNLOCK, 1); log.add(LogType.MSG_CRT_UNLOCK, 1);
certificationKey = secretKeyRing.getSecretKey(); certificationKey = secretKeyRing.getSecretKey();
if (certificationKey.getSecretKeyType() == SecretKeyType.DIVERT_TO_CARD) {
log.add(LogType.MSG_CRT_ERROR_DIVERT, 2);
return new CertifyResult(CertifyResult.RESULT_ERROR, log);
}
// certification is always with the master key id, so use that one // certification is always with the master key id, so use that one
String passphrase = getCachedPassphrase(parcel.mMasterKeyId, parcel.mMasterKeyId); String passphrase = getCachedPassphrase(parcel.mMasterKeyId, parcel.mMasterKeyId);
@ -102,6 +102,8 @@ public class CertifyOperation extends BaseOperation {
int certifyOk = 0, certifyError = 0, uploadOk = 0, uploadError = 0; int certifyOk = 0, certifyError = 0, uploadOk = 0, uploadError = 0;
NfcSignOperationsBuilder allRequiredInput = new NfcSignOperationsBuilder(parcel.getSignatureTime());
// Work through all requested certifications // Work through all requested certifications
for (CertifyAction action : parcel.mCertifyActions) { for (CertifyAction action : parcel.mCertifyActions) {
@ -122,28 +124,21 @@ public class CertifyOperation extends BaseOperation {
CanonicalizedPublicKeyRing publicRing = CanonicalizedPublicKeyRing publicRing =
mProviderHelper.getCanonicalizedPublicKeyRing(action.mMasterKeyId); mProviderHelper.getCanonicalizedPublicKeyRing(action.mMasterKeyId);
UncachedKeyRing certifiedKey = null; PgpCertifyOperation op = new PgpCertifyOperation();
if (action.mUserIds != null) { PgpCertifyResult result = op.certify(certificationKey, publicRing,
log.add(LogType.MSG_CRT_CERTIFY_UIDS, 2, action.mUserIds.size(), log, 2, action, parcel.getSignatureData(), parcel.getSignatureTime());
KeyFormattingUtils.convertKeyIdToHex(action.mMasterKeyId));
certifiedKey = certificationKey.certifyUserIds( if (!result.success()) {
publicRing, action.mUserIds, null, null);
}
if (action.mUserAttributes != null) {
log.add(LogType.MSG_CRT_CERTIFY_UATS, 2, action.mUserAttributes.size(),
KeyFormattingUtils.convertKeyIdToHex(action.mMasterKeyId));
certifiedKey = certificationKey.certifyUserAttributes(
publicRing, action.mUserAttributes, null, null);
}
if (certifiedKey == null) {
certifyError += 1; certifyError += 1;
log.add(LogType.MSG_CRT_WARN_CERT_FAILED, 3); continue;
} }
certifiedKeys.add(certifiedKey); if (result.nfcInputRequired()) {
NfcOperationsParcel requiredInput = result.getRequiredInput();
allRequiredInput.addAll(requiredInput);
continue;
}
certifiedKeys.add(result.getCertifiedRing());
} catch (NotFoundException e) { } catch (NotFoundException e) {
certifyError += 1; certifyError += 1;
@ -152,6 +147,11 @@ public class CertifyOperation extends BaseOperation {
} }
if ( ! allRequiredInput.isEmpty()) {
log.add(LogType.MSG_CRT_NFC_RETURN, 1);
return new CertifyResult(log, allRequiredInput.build());
}
log.add(LogType.MSG_CRT_SAVING, 1); log.add(LogType.MSG_CRT_SAVING, 1);
// Check if we were cancelled // Check if we were cancelled

View File

@ -23,6 +23,7 @@ import android.content.Intent;
import android.os.Parcel; import android.os.Parcel;
import org.sufficientlysecure.keychain.R; import org.sufficientlysecure.keychain.R;
import org.sufficientlysecure.keychain.service.input.NfcOperationsParcel;
import org.sufficientlysecure.keychain.ui.LogDisplayActivity; import org.sufficientlysecure.keychain.ui.LogDisplayActivity;
import org.sufficientlysecure.keychain.ui.LogDisplayFragment; import org.sufficientlysecure.keychain.ui.LogDisplayFragment;
import org.sufficientlysecure.keychain.ui.util.Notify; import org.sufficientlysecure.keychain.ui.util.Notify;
@ -30,16 +31,19 @@ import org.sufficientlysecure.keychain.ui.util.Notify.ActionListener;
import org.sufficientlysecure.keychain.ui.util.Notify.Showable; import org.sufficientlysecure.keychain.ui.util.Notify.Showable;
import org.sufficientlysecure.keychain.ui.util.Notify.Style; import org.sufficientlysecure.keychain.ui.util.Notify.Style;
public class CertifyResult extends OperationResult { public class CertifyResult extends InputPendingResult {
int mCertifyOk, mCertifyError, mUploadOk, mUploadError; int mCertifyOk, mCertifyError, mUploadOk, mUploadError;
public CertifyResult(int result, OperationLog log) { public CertifyResult(int result, OperationLog log) {
super(result, log); super(result, log);
} }
public CertifyResult(OperationLog log, NfcOperationsParcel requiredInput) {
super(log, requiredInput);
}
public CertifyResult(int result, OperationLog log, int certifyOk, int certifyError, int uploadOk, int uploadError) { public CertifyResult(int result, OperationLog log, int certifyOk, int certifyError, int uploadOk, int uploadError) {
this(result, log); super(result, log);
mCertifyOk = certifyOk; mCertifyOk = certifyOk;
mCertifyError = certifyError; mCertifyError = certifyError;
mUploadOk = uploadOk; mUploadOk = uploadOk;

View File

@ -0,0 +1,76 @@
package org.sufficientlysecure.keychain.operations.results;
import android.os.Parcel;
import org.sufficientlysecure.keychain.service.input.NfcOperationsParcel;
public class InputPendingResult extends OperationResult {
// the fourth bit indicates a "data pending" result! (it's also a form of non-success)
public static final int RESULT_PENDING = RESULT_ERROR + 8;
public static final int RESULT_PENDING_PASSPHRASE = RESULT_PENDING + 16;
public static final int RESULT_PENDING_NFC = RESULT_PENDING + 32;
final NfcOperationsParcel mRequiredInput;
final Long mKeyIdPassphraseNeeded;
public InputPendingResult(int result, OperationLog log) {
super(result, log);
mRequiredInput = null;
mKeyIdPassphraseNeeded = null;
}
public InputPendingResult(OperationLog log, NfcOperationsParcel requiredInput) {
super(RESULT_PENDING_NFC, log);
mRequiredInput = requiredInput;
mKeyIdPassphraseNeeded = null;
}
public InputPendingResult(OperationLog log, long keyIdPassphraseNeeded) {
super(RESULT_PENDING_PASSPHRASE, log);
mRequiredInput = null;
mKeyIdPassphraseNeeded = keyIdPassphraseNeeded;
}
public InputPendingResult(Parcel source) {
super(source);
mRequiredInput = source.readParcelable(getClass().getClassLoader());
mKeyIdPassphraseNeeded = source.readInt() != 0 ? source.readLong() : null;
}
@Override
public void writeToParcel(Parcel dest, int flags) {
super.writeToParcel(dest, flags);
dest.writeParcelable(mRequiredInput, 0);
if (mKeyIdPassphraseNeeded != null) {
dest.writeInt(1);
dest.writeLong(mKeyIdPassphraseNeeded);
} else {
dest.writeInt(0);
}
}
public boolean isPending() {
return (mResult & RESULT_PENDING) == RESULT_PENDING;
}
public boolean isNfcPending() {
return (mResult & RESULT_PENDING_NFC) == RESULT_PENDING_NFC;
}
public boolean isPassphrasePending() {
return (mResult & RESULT_PENDING_PASSPHRASE) == RESULT_PENDING_PASSPHRASE;
}
public NfcOperationsParcel getNfcOperationsParcel() {
return mRequiredInput;
}
public long getPassphraseKeyId() {
return mKeyIdPassphraseNeeded;
}
}

View File

@ -697,9 +697,9 @@ public abstract class OperationResult implements Parcelable {
MSG_CRT_ERROR_MASTER_NOT_FOUND (LogLevel.ERROR, R.string.msg_crt_error_master_not_found), MSG_CRT_ERROR_MASTER_NOT_FOUND (LogLevel.ERROR, R.string.msg_crt_error_master_not_found),
MSG_CRT_ERROR_NOTHING (LogLevel.ERROR, R.string.msg_crt_error_nothing), MSG_CRT_ERROR_NOTHING (LogLevel.ERROR, R.string.msg_crt_error_nothing),
MSG_CRT_ERROR_UNLOCK (LogLevel.ERROR, R.string.msg_crt_error_unlock), MSG_CRT_ERROR_UNLOCK (LogLevel.ERROR, R.string.msg_crt_error_unlock),
MSG_CRT_ERROR_DIVERT (LogLevel.ERROR, R.string.msg_crt_error_divert),
MSG_CRT (LogLevel.START, R.string.msg_crt), MSG_CRT (LogLevel.START, R.string.msg_crt),
MSG_CRT_MASTER_FETCH (LogLevel.DEBUG, R.string.msg_crt_master_fetch), MSG_CRT_MASTER_FETCH (LogLevel.DEBUG, R.string.msg_crt_master_fetch),
MSG_CRT_NFC_RETURN (LogLevel.OK, R.string.msg_crt_nfc_return),
MSG_CRT_SAVE (LogLevel.DEBUG, R.string.msg_crt_save), MSG_CRT_SAVE (LogLevel.DEBUG, R.string.msg_crt_save),
MSG_CRT_SAVING (LogLevel.DEBUG, R.string.msg_crt_saving), MSG_CRT_SAVING (LogLevel.DEBUG, R.string.msg_crt_saving),
MSG_CRT_SUCCESS (LogLevel.OK, R.string.msg_crt_success), MSG_CRT_SUCCESS (LogLevel.OK, R.string.msg_crt_success),

View File

@ -31,6 +31,7 @@ public class SignEncryptResult extends OperationResult {
public static final int RESULT_PENDING = RESULT_ERROR + 8; public static final int RESULT_PENDING = RESULT_ERROR + 8;
public PgpSignEncryptResult getPending() { public PgpSignEncryptResult getPending() {
for (PgpSignEncryptResult sub : mResults) { for (PgpSignEncryptResult sub : mResults) {
if (sub.isPending()) { if (sub.isPending()) {

View File

@ -202,7 +202,25 @@ public class CanonicalizedSecretKey extends CanonicalizedPublicKey {
} }
} }
public PGPSignatureGenerator getSignatureGenerator(int hashAlgo, boolean cleartext, public PGPSignatureGenerator getCertSignatureGenerator(Map<ByteBuffer, byte[]> signedHashes) {
PGPContentSignerBuilder contentSignerBuilder = getContentSignerBuilder(
PgpConstants.CERTIFY_HASH_ALGO, signedHashes);
if (mPrivateKeyState == PRIVATE_KEY_STATE_LOCKED) {
throw new PrivateKeyNotUnlockedException();
}
PGPSignatureGenerator signatureGenerator = new PGPSignatureGenerator(contentSignerBuilder);
try {
signatureGenerator.init(PGPSignature.DEFAULT_CERTIFICATION, mPrivateKey);
return signatureGenerator;
} catch (PGPException e) {
Log.e(Constants.TAG, "signing error", e);
return null;
}
}
public PGPSignatureGenerator getDataSignatureGenerator(int hashAlgo, boolean cleartext,
Map<ByteBuffer, byte[]> signedHashes, Date creationTimestamp) Map<ByteBuffer, byte[]> signedHashes, Date creationTimestamp)
throws PgpGeneralException { throws PgpGeneralException {
if (mPrivateKeyState == PRIVATE_KEY_STATE_LOCKED) { if (mPrivateKeyState == PRIVATE_KEY_STATE_LOCKED) {
@ -259,135 +277,6 @@ public class CanonicalizedSecretKey extends CanonicalizedPublicKey {
} }
} }
/**
* Certify the given pubkeyid with the given masterkeyid.
*
* @param publicKeyRing Keyring to add certification to.
* @param userIds User IDs to certify
* @return A keyring with added certifications
*/
public UncachedKeyRing certifyUserIds(CanonicalizedPublicKeyRing publicKeyRing,
List<String> userIds,
HashMap<ByteBuffer,byte[]> signedHashes, Date creationTimestamp) {
if (mPrivateKeyState == PRIVATE_KEY_STATE_LOCKED) {
throw new PrivateKeyNotUnlockedException();
}
if (!isMasterKey()) {
throw new AssertionError("tried to certify with non-master key, this is a programming error!");
}
if (publicKeyRing.getMasterKeyId() == getKeyId()) {
throw new AssertionError("key tried to self-certify, this is a programming error!");
}
// create a signatureGenerator from the supplied masterKeyId and passphrase
PGPSignatureGenerator signatureGenerator;
{
PGPContentSignerBuilder contentSignerBuilder = getContentSignerBuilder(
PgpConstants.CERTIFY_HASH_ALGO, signedHashes);
signatureGenerator = new PGPSignatureGenerator(contentSignerBuilder);
try {
signatureGenerator.init(PGPSignature.DEFAULT_CERTIFICATION, mPrivateKey);
} catch (PGPException e) {
Log.e(Constants.TAG, "signing error", e);
return null;
}
}
{ // supply signatureGenerator with a SubpacketVector
PGPSignatureSubpacketGenerator spGen = new PGPSignatureSubpacketGenerator();
if (creationTimestamp != null) {
spGen.setSignatureCreationTime(false, creationTimestamp);
Log.d(Constants.TAG, "For NFC: set sig creation time to " + creationTimestamp);
}
PGPSignatureSubpacketVector packetVector = spGen.generate();
signatureGenerator.setHashedSubpackets(packetVector);
}
// get the master subkey (which we certify for)
PGPPublicKey publicKey = publicKeyRing.getPublicKey().getPublicKey();
// fetch public key ring, add the certification and return it
try {
for (String userId : userIds) {
PGPSignature sig = signatureGenerator.generateCertification(userId, publicKey);
publicKey = PGPPublicKey.addCertification(publicKey, userId, sig);
}
} catch (PGPException e) {
Log.e(Constants.TAG, "signing error", e);
return null;
}
PGPPublicKeyRing ring = PGPPublicKeyRing.insertPublicKey(publicKeyRing.getRing(), publicKey);
return new UncachedKeyRing(ring);
}
/**
* Certify the given user attributes with the given masterkeyid.
*
* @param publicKeyRing Keyring to add certification to.
* @param userAttributes User IDs to certify, or all if null
* @return A keyring with added certifications
*/
public UncachedKeyRing certifyUserAttributes(CanonicalizedPublicKeyRing publicKeyRing,
List<WrappedUserAttribute> userAttributes,
HashMap<ByteBuffer,byte[]> signedHashes, Date creationTimestamp) {
if (mPrivateKeyState == PRIVATE_KEY_STATE_LOCKED) {
throw new PrivateKeyNotUnlockedException();
}
if (!isMasterKey()) {
throw new AssertionError("tried to certify with non-master key, this is a programming error!");
}
if (publicKeyRing.getMasterKeyId() == getKeyId()) {
throw new AssertionError("key tried to self-certify, this is a programming error!");
}
// create a signatureGenerator from the supplied masterKeyId and passphrase
PGPSignatureGenerator signatureGenerator;
{
PGPContentSignerBuilder contentSignerBuilder = getContentSignerBuilder(
PgpConstants.CERTIFY_HASH_ALGO, signedHashes);
signatureGenerator = new PGPSignatureGenerator(contentSignerBuilder);
try {
signatureGenerator.init(PGPSignature.DEFAULT_CERTIFICATION, mPrivateKey);
} catch (PGPException e) {
Log.e(Constants.TAG, "signing error", e);
return null;
}
}
{ // supply signatureGenerator with a SubpacketVector
PGPSignatureSubpacketGenerator spGen = new PGPSignatureSubpacketGenerator();
if (creationTimestamp != null) {
spGen.setSignatureCreationTime(false, creationTimestamp);
Log.d(Constants.TAG, "For NFC: set sig creation time to " + creationTimestamp);
}
PGPSignatureSubpacketVector packetVector = spGen.generate();
signatureGenerator.setHashedSubpackets(packetVector);
}
// get the master subkey (which we certify for)
PGPPublicKey publicKey = publicKeyRing.getPublicKey().getPublicKey();
// fetch public key ring, add the certification and return it
try {
for (WrappedUserAttribute userAttribute : userAttributes) {
PGPUserAttributeSubpacketVector vector = userAttribute.getVector();
PGPSignature sig = signatureGenerator.generateCertification(vector, publicKey);
publicKey = PGPPublicKey.addCertification(publicKey, vector, sig);
}
} catch (PGPException e) {
Log.e(Constants.TAG, "signing error", e);
return null;
}
PGPPublicKeyRing ring = PGPPublicKeyRing.insertPublicKey(publicKeyRing.getRing(), publicKey);
return new UncachedKeyRing(ring);
}
static class PrivateKeyNotUnlockedException extends RuntimeException { static class PrivateKeyNotUnlockedException extends RuntimeException {
// this exception is a programming error which happens when an operation which requires // this exception is a programming error which happens when an operation which requires
// the private key is called without a previous call to unlock() // the private key is called without a previous call to unlock()

View File

@ -0,0 +1,149 @@
package org.sufficientlysecure.keychain.pgp;
import java.nio.ByteBuffer;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;
import org.spongycastle.openpgp.PGPException;
import org.spongycastle.openpgp.PGPPublicKey;
import org.spongycastle.openpgp.PGPPublicKeyRing;
import org.spongycastle.openpgp.PGPSignature;
import org.spongycastle.openpgp.PGPSignatureGenerator;
import org.spongycastle.openpgp.PGPSignatureSubpacketGenerator;
import org.spongycastle.openpgp.PGPSignatureSubpacketVector;
import org.spongycastle.openpgp.PGPUserAttributeSubpacketVector;
import org.spongycastle.openpgp.operator.jcajce.NfcSyncPGPContentSignerBuilder.NfcInteractionNeeded;
import org.sufficientlysecure.keychain.Constants;
import org.sufficientlysecure.keychain.operations.results.OperationResult.LogType;
import org.sufficientlysecure.keychain.operations.results.OperationResult.OperationLog;
import org.sufficientlysecure.keychain.service.CertifyActionsParcel.CertifyAction;
import org.sufficientlysecure.keychain.service.input.NfcOperationsParcel;
import org.sufficientlysecure.keychain.service.input.NfcOperationsParcel.NfcSignOperationsBuilder;
import org.sufficientlysecure.keychain.ui.util.KeyFormattingUtils;
import org.sufficientlysecure.keychain.util.Log;
public class PgpCertifyOperation {
public PgpCertifyResult certify(
CanonicalizedSecretKey secretKey,
CanonicalizedPublicKeyRing publicRing,
OperationLog log,
int indent,
CertifyAction action,
Map<ByteBuffer,byte[]> signedHashes,
Date creationTimestamp) {
if (!secretKey.isMasterKey()) {
throw new AssertionError("tried to certify with non-master key, this is a programming error!");
}
if (publicRing.getMasterKeyId() == secretKey.getKeyId()) {
throw new AssertionError("key tried to self-certify, this is a programming error!");
}
// create a signatureGenerator from the supplied masterKeyId and passphrase
PGPSignatureGenerator signatureGenerator = secretKey.getCertSignatureGenerator(signedHashes);
{ // supply signatureGenerator with a SubpacketVector
PGPSignatureSubpacketGenerator spGen = new PGPSignatureSubpacketGenerator();
if (creationTimestamp != null) {
spGen.setSignatureCreationTime(false, creationTimestamp);
Log.d(Constants.TAG, "For NFC: set sig creation time to " + creationTimestamp);
}
PGPSignatureSubpacketVector packetVector = spGen.generate();
signatureGenerator.setHashedSubpackets(packetVector);
}
// get the master subkey (which we certify for)
PGPPublicKey publicKey = publicRing.getPublicKey().getPublicKey();
NfcSignOperationsBuilder requiredInput = new NfcSignOperationsBuilder(creationTimestamp);
try {
if (action.mUserIds != null) {
log.add(LogType.MSG_CRT_CERTIFY_UIDS, 2, action.mUserIds.size(),
KeyFormattingUtils.convertKeyIdToHex(action.mMasterKeyId));
// fetch public key ring, add the certification and return it
for (String userId : action.mUserIds) {
try {
PGPSignature sig = signatureGenerator.generateCertification(userId, publicKey);
publicKey = PGPPublicKey.addCertification(publicKey, userId, sig);
} catch (NfcInteractionNeeded e) {
requiredInput.addHash(e.hashToSign, e.hashAlgo);
}
}
}
if (action.mUserAttributes != null) {
log.add(LogType.MSG_CRT_CERTIFY_UATS, 2, action.mUserAttributes.size(),
KeyFormattingUtils.convertKeyIdToHex(action.mMasterKeyId));
// fetch public key ring, add the certification and return it
for (WrappedUserAttribute userAttribute : action.mUserAttributes) {
PGPUserAttributeSubpacketVector vector = userAttribute.getVector();
try {
PGPSignature sig = signatureGenerator.generateCertification(vector, publicKey);
publicKey = PGPPublicKey.addCertification(publicKey, vector, sig);
} catch (NfcInteractionNeeded e) {
requiredInput.addHash(e.hashToSign, e.hashAlgo);
}
}
}
} catch (PGPException e) {
Log.e(Constants.TAG, "signing error", e);
return new PgpCertifyResult();
}
if (!requiredInput.isEmpty()) {
return new PgpCertifyResult(requiredInput.build());
}
PGPPublicKeyRing ring = PGPPublicKeyRing.insertPublicKey(publicRing.getRing(), publicKey);
return new PgpCertifyResult(new UncachedKeyRing(ring));
}
public static class PgpCertifyResult {
final NfcOperationsParcel mRequiredInput;
final UncachedKeyRing mCertifiedRing;
PgpCertifyResult() {
mRequiredInput = null;
mCertifiedRing = null;
}
PgpCertifyResult(NfcOperationsParcel requiredInput) {
mRequiredInput = requiredInput;
mCertifiedRing = null;
}
PgpCertifyResult(UncachedKeyRing certifiedRing) {
mRequiredInput = null;
mCertifiedRing = certifiedRing;
}
public boolean success() {
return mCertifiedRing != null || mRequiredInput != null;
}
public boolean nfcInputRequired() {
return mRequiredInput != null;
}
public UncachedKeyRing getCertifiedRing() {
return mCertifiedRing;
}
public NfcOperationsParcel getRequiredInput() {
return mRequiredInput;
}
}
}

View File

@ -44,8 +44,6 @@ import org.sufficientlysecure.keychain.pgp.exception.PgpGeneralException;
import org.sufficientlysecure.keychain.pgp.exception.PgpKeyNotFoundException; import org.sufficientlysecure.keychain.pgp.exception.PgpKeyNotFoundException;
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.service.input.CryptoInputParcel;
import org.sufficientlysecure.keychain.service.input.NfcOperationsParcel;
import org.sufficientlysecure.keychain.ui.util.KeyFormattingUtils; import org.sufficientlysecure.keychain.ui.util.KeyFormattingUtils;
import org.sufficientlysecure.keychain.util.InputData; import org.sufficientlysecure.keychain.util.InputData;
import org.sufficientlysecure.keychain.util.Log; import org.sufficientlysecure.keychain.util.Log;
@ -283,7 +281,7 @@ public class PgpSignEncryptOperation extends BaseOperation {
try { try {
boolean cleartext = input.isCleartextSignature() && input.isEnableAsciiArmorOutput() && !enableEncryption; boolean cleartext = input.isCleartextSignature() && input.isEnableAsciiArmorOutput() && !enableEncryption;
signatureGenerator = signingKey.getSignatureGenerator( signatureGenerator = signingKey.getDataSignatureGenerator(
input.getSignatureHashAlgorithm(), cleartext, input.getSignatureHashAlgorithm(), cleartext,
input.getCryptoData(), input.getSignatureTime()); input.getCryptoData(), input.getSignatureTime());
} catch (PgpGeneralException e) { } catch (PgpGeneralException e) {

View File

@ -22,8 +22,10 @@ import android.os.Parcel;
import android.os.Parcelable; import android.os.Parcelable;
import java.io.Serializable; import java.io.Serializable;
import java.nio.ByteBuffer;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Date; import java.util.Date;
import java.util.Map;
import org.sufficientlysecure.keychain.pgp.WrappedUserAttribute; import org.sufficientlysecure.keychain.pgp.WrappedUserAttribute;
import org.sufficientlysecure.keychain.service.input.CryptoInputParcel; import org.sufficientlysecure.keychain.service.input.CryptoInputParcel;
@ -42,9 +44,9 @@ public class CertifyActionsParcel implements Parcelable {
public ArrayList<CertifyAction> mCertifyActions = new ArrayList<>(); public ArrayList<CertifyAction> mCertifyActions = new ArrayList<>();
public CryptoInputParcel mCryptoInput; public CryptoInputParcel mCryptoInput;
public CertifyActionsParcel(Date operationTime, long masterKeyId) { public CertifyActionsParcel(CryptoInputParcel cryptoInput, long masterKeyId) {
mMasterKeyId = masterKeyId; mMasterKeyId = masterKeyId;
mCryptoInput = new CryptoInputParcel(operationTime); mCryptoInput = cryptoInput != null ? cryptoInput : new CryptoInputParcel(new Date());
mLevel = CertifyLevel.DEFAULT; mLevel = CertifyLevel.DEFAULT;
} }
@ -70,6 +72,14 @@ public class CertifyActionsParcel implements Parcelable {
destination.writeSerializable(mCertifyActions); destination.writeSerializable(mCertifyActions);
} }
public Map<ByteBuffer, byte[]> getSignatureData() {
return mCryptoInput.getCryptoData();
}
public Date getSignatureTime() {
return mCryptoInput.getSignatureTime();
}
public static final Creator<CertifyActionsParcel> CREATOR = new Creator<CertifyActionsParcel>() { public static final Creator<CertifyActionsParcel> CREATOR = new Creator<CertifyActionsParcel>() {
public CertifyActionsParcel createFromParcel(final Parcel source) { public CertifyActionsParcel createFromParcel(final Parcel source) {
return new CertifyActionsParcel(source); return new CertifyActionsParcel(source);

View File

@ -18,6 +18,7 @@
package org.sufficientlysecure.keychain.service; package org.sufficientlysecure.keychain.service;
import android.app.Activity; import android.app.Activity;
import android.content.Intent;
import android.os.Bundle; import android.os.Bundle;
import android.os.Handler; import android.os.Handler;
import android.os.Message; import android.os.Message;
@ -26,6 +27,7 @@ import android.support.v4.app.FragmentManager;
import org.sufficientlysecure.keychain.Constants; import org.sufficientlysecure.keychain.Constants;
import org.sufficientlysecure.keychain.R; import org.sufficientlysecure.keychain.R;
import org.sufficientlysecure.keychain.operations.results.CertifyResult;
import org.sufficientlysecure.keychain.ui.dialog.ProgressDialogFragment; import org.sufficientlysecure.keychain.ui.dialog.ProgressDialogFragment;
import org.sufficientlysecure.keychain.ui.util.Notify; import org.sufficientlysecure.keychain.ui.util.Notify;
import org.sufficientlysecure.keychain.util.Log; import org.sufficientlysecure.keychain.util.Log;

View File

@ -1,5 +1,7 @@
package org.sufficientlysecure.keychain.service.input; package org.sufficientlysecure.keychain.service.input;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Date; import java.util.Date;
import android.os.Parcel; import android.os.Parcel;
@ -14,13 +16,14 @@ public class NfcOperationsParcel implements Parcelable {
public Date mSignatureTime; public Date mSignatureTime;
public final NfcOperationType mType; public final NfcOperationType mType;
public final byte[][] mInputHash; public final byte[][] mInputHashes;
public final int[] mSignAlgo; public final int[] mSignAlgos;
private NfcOperationsParcel(NfcOperationType type, byte[] inputHash, int signAlgo, Date signatureTime) { private NfcOperationsParcel(NfcOperationType type, byte[][] inputHashes,
int[] signAlgos, Date signatureTime) {
mType = type; mType = type;
mInputHash = new byte[][] { inputHash }; mInputHashes = inputHashes;
mSignAlgo = new int[] { signAlgo }; mSignAlgos = signAlgos;
mSignatureTime = signatureTime; mSignatureTime = signatureTime;
} }
@ -29,11 +32,11 @@ public class NfcOperationsParcel implements Parcelable {
{ {
int count = source.readInt(); int count = source.readInt();
mInputHash = new byte[count][]; mInputHashes = new byte[count][];
mSignAlgo = new int[count]; mSignAlgos = new int[count];
for (int i = 0; i < count; i++) { for (int i = 0; i < count; i++) {
mInputHash[i] = source.createByteArray(); mInputHashes[i] = source.createByteArray();
mSignAlgo[i] = source.readInt(); mSignAlgos[i] = source.readInt();
} }
} }
@ -43,11 +46,13 @@ public class NfcOperationsParcel implements Parcelable {
public static NfcOperationsParcel createNfcSignOperation( public static NfcOperationsParcel createNfcSignOperation(
byte[] inputHash, int signAlgo, Date signatureTime) { byte[] inputHash, int signAlgo, Date signatureTime) {
return new NfcOperationsParcel(NfcOperationType.NFC_SIGN, inputHash, signAlgo, signatureTime); return new NfcOperationsParcel(NfcOperationType.NFC_SIGN,
new byte[][] { inputHash }, new int[] { signAlgo }, signatureTime);
} }
public static NfcOperationsParcel createNfcDecryptOperation(byte[] inputHash) { public static NfcOperationsParcel createNfcDecryptOperation(byte[] inputHash) {
return new NfcOperationsParcel(NfcOperationType.NFC_DECRYPT, inputHash, 0, null); return new NfcOperationsParcel(NfcOperationType.NFC_DECRYPT,
new byte[][] { inputHash }, null, null);
} }
@Override @Override
@ -58,10 +63,10 @@ public class NfcOperationsParcel implements Parcelable {
@Override @Override
public void writeToParcel(Parcel dest, int flags) { public void writeToParcel(Parcel dest, int flags) {
dest.writeInt(mType.ordinal()); dest.writeInt(mType.ordinal());
dest.writeInt(mInputHash.length); dest.writeInt(mInputHashes.length);
for (int i = 0; i < mInputHash.length; i++) { for (int i = 0; i < mInputHashes.length; i++) {
dest.writeByteArray(mInputHash[i]); dest.writeByteArray(mInputHashes[i]);
dest.writeInt(mSignAlgo[i]); dest.writeInt(mSignAlgos[i]);
} }
if (mSignatureTime != null) { if (mSignatureTime != null) {
dest.writeInt(1); dest.writeInt(1);
@ -82,4 +87,50 @@ public class NfcOperationsParcel implements Parcelable {
} }
}; };
public static class NfcSignOperationsBuilder {
Date mSignatureTime;
ArrayList<Integer> mSignAlgos = new ArrayList<>();
ArrayList<byte[]> mInputHashes = new ArrayList<>();
public NfcSignOperationsBuilder(Date signatureTime) {
mSignatureTime = signatureTime;
}
public NfcOperationsParcel build() {
byte[][] inputHashes = new byte[mInputHashes.size()][];
mInputHashes.toArray(inputHashes);
int[] signAlgos = new int[mSignAlgos.size()];
for (int i = 0; i < mSignAlgos.size(); i++) {
signAlgos[i] = mSignAlgos.get(i);
}
return new NfcOperationsParcel(NfcOperationType.NFC_SIGN,
inputHashes, signAlgos, mSignatureTime);
}
public void addHash(byte[] hash, int algo) {
mInputHashes.add(hash);
mSignAlgos.add(algo);
}
public void addAll(NfcOperationsParcel input) {
if (!mSignatureTime.equals(input.mSignatureTime)) {
throw new AssertionError("input times must match, this is a programming error!");
}
if (input.mType != NfcOperationType.NFC_SIGN) {
throw new AssertionError("operation types must match, this is a progrmming error!");
}
Collections.addAll(mInputHashes, input.mInputHashes);
for (int signAlgo : input.mSignAlgos) {
mSignAlgos.add(signAlgo);
}
}
public boolean isEmpty() {
return mInputHashes.isEmpty();
}
}
} }

View File

@ -56,7 +56,7 @@ import org.sufficientlysecure.keychain.service.CertifyActionsParcel;
import org.sufficientlysecure.keychain.service.CertifyActionsParcel.CertifyAction; import org.sufficientlysecure.keychain.service.CertifyActionsParcel.CertifyAction;
import org.sufficientlysecure.keychain.service.KeychainIntentService; import org.sufficientlysecure.keychain.service.KeychainIntentService;
import org.sufficientlysecure.keychain.service.KeychainIntentServiceHandler; import org.sufficientlysecure.keychain.service.KeychainIntentServiceHandler;
import org.sufficientlysecure.keychain.service.PassphraseCacheService; import org.sufficientlysecure.keychain.service.input.CryptoInputParcel;
import org.sufficientlysecure.keychain.ui.adapter.MultiUserIdsAdapter; import org.sufficientlysecure.keychain.ui.adapter.MultiUserIdsAdapter;
import org.sufficientlysecure.keychain.ui.util.Notify; import org.sufficientlysecure.keychain.ui.util.Notify;
import org.sufficientlysecure.keychain.ui.widget.CertifyKeySpinner; import org.sufficientlysecure.keychain.ui.widget.CertifyKeySpinner;
@ -66,14 +66,11 @@ import org.sufficientlysecure.keychain.util.Preferences;
import java.lang.reflect.Method; import java.lang.reflect.Method;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Date;
public class CertifyKeyFragment extends LoaderFragment public class CertifyKeyFragment extends CryptoOperationFragment
implements LoaderManager.LoaderCallbacks<Cursor> { implements LoaderManager.LoaderCallbacks<Cursor> {
public static final int REQUEST_CODE_PASSPHRASE = 0x00008001;
private CheckBox mUploadKeyCheckbox; private CheckBox mUploadKeyCheckbox;
ListView mUserIds; ListView mUserIds;
@ -102,9 +99,6 @@ public class CertifyKeyFragment extends LoaderFragment
public void onActivityCreated(Bundle savedInstanceState) { public void onActivityCreated(Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState); super.onActivityCreated(savedInstanceState);
// Start out with a progress indicator.
setContentShown(false);
mPubMasterKeyIds = getActivity().getIntent().getLongArrayExtra(CertifyKeyActivity.EXTRA_KEY_IDS); mPubMasterKeyIds = getActivity().getIntent().getLongArrayExtra(CertifyKeyActivity.EXTRA_KEY_IDS);
if (mPubMasterKeyIds == null) { if (mPubMasterKeyIds == null) {
Log.e(Constants.TAG, "List of key ids to certify missing!"); Log.e(Constants.TAG, "List of key ids to certify missing!");
@ -114,6 +108,7 @@ public class CertifyKeyFragment extends LoaderFragment
mPassthroughMessenger = getActivity().getIntent().getParcelableExtra( mPassthroughMessenger = getActivity().getIntent().getParcelableExtra(
KeychainIntentService.EXTRA_MESSENGER); KeychainIntentService.EXTRA_MESSENGER);
mPassthroughMessenger = null; // TODO remove, development hack
// preselect certify key id if given // preselect certify key id if given
long certifyKeyId = getActivity().getIntent().getLongExtra(CertifyKeyActivity.EXTRA_CERTIFY_KEY_ID, Constants.key.none); long certifyKeyId = getActivity().getIntent().getLongExtra(CertifyKeyActivity.EXTRA_CERTIFY_KEY_ID, Constants.key.none);
@ -143,9 +138,7 @@ public class CertifyKeyFragment extends LoaderFragment
@Override @Override
public View onCreateView(LayoutInflater inflater, ViewGroup superContainer, Bundle savedInstanceState) { public View onCreateView(LayoutInflater inflater, ViewGroup superContainer, Bundle savedInstanceState) {
View root = super.onCreateView(inflater, superContainer, savedInstanceState); View view = inflater.inflate(R.layout.certify_key_fragment, null);
View view = inflater.inflate(R.layout.certify_key_fragment, getContainer());
mCertifyKeySpinner = (CertifyKeySpinner) view.findViewById(R.id.certify_key_spinner); mCertifyKeySpinner = (CertifyKeySpinner) view.findViewById(R.id.certify_key_spinner);
mUploadKeyCheckbox = (CheckBox) view.findViewById(R.id.sign_key_upload_checkbox); mUploadKeyCheckbox = (CheckBox) view.findViewById(R.id.sign_key_upload_checkbox);
@ -173,7 +166,7 @@ public class CertifyKeyFragment extends LoaderFragment
Notify.showNotify(getActivity(), getString(R.string.select_key_to_certify), Notify.showNotify(getActivity(), getString(R.string.select_key_to_certify),
Notify.Style.ERROR); Notify.Style.ERROR);
} else { } else {
initiateCertifying(); cryptoOperation();
} }
} }
}); });
@ -183,7 +176,7 @@ public class CertifyKeyFragment extends LoaderFragment
mUploadKeyCheckbox.setChecked(false); mUploadKeyCheckbox.setChecked(false);
} }
return root; return view;
} }
@Override @Override
@ -307,7 +300,6 @@ public class CertifyKeyFragment extends LoaderFragment
} }
mUserIdsAdapter.swapCursor(matrix); mUserIdsAdapter.swapCursor(matrix);
setContentShown(true, isResumed());
} }
@Override @Override
@ -315,50 +307,17 @@ public class CertifyKeyFragment extends LoaderFragment
mUserIdsAdapter.swapCursor(null); mUserIdsAdapter.swapCursor(null);
} }
/** protected void cryptoOperation() {
* handles the UI bits of the signing process on the UI thread cryptoOperation((CryptoInputParcel) null);
*/
private void initiateCertifying() {
// get the user's passphrase for this key (if required)
String passphrase;
try {
passphrase = PassphraseCacheService.getCachedPassphrase(getActivity(), mSignMasterKeyId, mSignMasterKeyId);
} catch (PassphraseCacheService.KeyNotFoundException e) {
Log.e(Constants.TAG, "Key not found!", e);
getActivity().finish();
return;
}
if (passphrase == null) {
Intent intent = new Intent(getActivity(), PassphraseDialogActivity.class);
intent.putExtra(PassphraseDialogActivity.EXTRA_SUBKEY_ID, mSignMasterKeyId);
startActivityForResult(intent, REQUEST_CODE_PASSPHRASE);
// bail out; need to wait until the user has entered the passphrase before trying again
} else {
startCertifying();
}
} }
@Override @Override
public void onActivityResult(int requestCode, int resultCode, Intent data) { protected void cryptoOperation(String passphrase) {
switch (requestCode) { cryptoOperation((CryptoInputParcel) null);
case REQUEST_CODE_PASSPHRASE: {
if (resultCode == Activity.RESULT_OK && data != null) {
String passphrase = data.getStringExtra(PassphraseDialogActivity.MESSAGE_DATA_PASSPHRASE);
startCertifying();
}
return;
} }
default: { @Override
super.onActivityResult(requestCode, resultCode, data); protected void cryptoOperation(CryptoInputParcel cryptoInput) {
}
}
}
/**
* kicks off the actual signing process on a background thread
*/
private void startCertifying() {
// Bail out if there is not at least one user id selected // Bail out if there is not at least one user id selected
ArrayList<CertifyAction> certifyActions = mUserIdsAdapter.getSelectedCertifyActions(); ArrayList<CertifyAction> certifyActions = mUserIdsAdapter.getSelectedCertifyActions();
if (certifyActions.isEmpty()) { if (certifyActions.isEmpty()) {
@ -370,7 +329,7 @@ public class CertifyKeyFragment extends LoaderFragment
Bundle data = new Bundle(); Bundle data = new Bundle();
{ {
// fill values for this action // fill values for this action
CertifyActionsParcel parcel = new CertifyActionsParcel(new Date(), mSignMasterKeyId); CertifyActionsParcel parcel = new CertifyActionsParcel(cryptoInput, mSignMasterKeyId);
parcel.mCertifyActions.addAll(certifyActions); parcel.mCertifyActions.addAll(certifyActions);
data.putParcelable(KeychainIntentService.CERTIFY_PARCEL, parcel); data.putParcelable(KeychainIntentService.CERTIFY_PARCEL, parcel);
@ -390,14 +349,21 @@ public class CertifyKeyFragment extends LoaderFragment
} else { } else {
// Message is received after signing is done in KeychainIntentService // Message is received after signing is done in KeychainIntentService
KeychainIntentServiceHandler saveHandler = new KeychainIntentServiceHandler(getActivity(), KeychainIntentServiceHandler saveHandler = new KeychainIntentServiceHandler(
getString(R.string.progress_certifying), ProgressDialog.STYLE_SPINNER, true) { getActivity(), getString(R.string.progress_certifying),
ProgressDialog.STYLE_SPINNER, true) {
public void handleMessage(Message message) { public void handleMessage(Message message) {
// handle messages by standard KeychainIntentServiceHandler first // handle messages by KeychainIntentCryptoServiceHandler first
super.handleMessage(message); super.handleMessage(message);
// handle pending messages
if (handlePendingMessage(message)) {
return;
}
if (message.arg1 == MessageStatus.OKAY.ordinal()) { if (message.arg1 == MessageStatus.OKAY.ordinal()) {
Bundle data = message.getData(); Bundle data = message.getData();
CertifyResult result = data.getParcelable(CertifyResult.EXTRA_RESULT); CertifyResult result = data.getParcelable(CertifyResult.EXTRA_RESULT);
Intent intent = new Intent(); Intent intent = new Intent();

View File

@ -0,0 +1,89 @@
package org.sufficientlysecure.keychain.ui;
import android.app.Activity;
import android.content.Intent;
import android.os.Bundle;
import android.os.Message;
import android.support.v4.app.Fragment;
import org.sufficientlysecure.keychain.operations.results.CertifyResult;
import org.sufficientlysecure.keychain.operations.results.InputPendingResult;
import org.sufficientlysecure.keychain.service.KeychainIntentServiceHandler.MessageStatus;
import org.sufficientlysecure.keychain.service.input.CryptoInputParcel;
import org.sufficientlysecure.keychain.service.input.NfcOperationsParcel;
public abstract class CryptoOperationFragment extends Fragment {
public static final int REQUEST_CODE_PASSPHRASE = 0x00008001;
public static final int REQUEST_CODE_NFC = 0x00008002;
private void startPassphraseDialog(long subkeyId) {
Intent intent = new Intent(getActivity(), PassphraseDialogActivity.class);
intent.putExtra(PassphraseDialogActivity.EXTRA_SUBKEY_ID, subkeyId);
startActivityForResult(intent, REQUEST_CODE_PASSPHRASE);
}
private void initiateNfcInput(NfcOperationsParcel nfcOps) {
Intent intent = new Intent(getActivity(), NfcOperationActivity.class);
intent.putExtra(NfcOperationActivity.EXTRA_PIN, "123456");
intent.putExtra(NfcOperationActivity.EXTRA_NFC_OPS, nfcOps);
startActivityForResult(intent, REQUEST_CODE_NFC);
}
@Override
public void onActivityResult(int requestCode, int resultCode, Intent data) {
switch (requestCode) {
case REQUEST_CODE_PASSPHRASE: {
if (resultCode == Activity.RESULT_OK && data != null) {
String passphrase = data.getStringExtra(PassphraseDialogActivity.MESSAGE_DATA_PASSPHRASE);
cryptoOperation(passphrase);
}
return;
}
case REQUEST_CODE_NFC: {
if (resultCode == Activity.RESULT_OK && data != null) {
CryptoInputParcel cryptoInput =
data.getParcelableExtra(NfcOperationActivity.RESULT_DATA);
cryptoOperation(cryptoInput);
return;
}
break;
}
default: {
super.onActivityResult(requestCode, resultCode, data);
}
}
}
public boolean handlePendingMessage(Message message) {
if (message.arg1 == MessageStatus.OKAY.ordinal()) {
Bundle data = message.getData();
InputPendingResult result = data.getParcelable(CertifyResult.EXTRA_RESULT);
if (result != null && result.isPending()) {
if (result.isPassphrasePending()) {
startPassphraseDialog(result.getPassphraseKeyId());
return true;
} else if (result.isNfcPending()) {
NfcOperationsParcel requiredInput = result.getNfcOperationsParcel();
initiateNfcInput(requiredInput);
return true;
} else {
throw new RuntimeException("Unhandled pending result!");
}
}
}
return false;
}
protected abstract void cryptoOperation(CryptoInputParcel cryptoInput);
protected abstract void cryptoOperation(String passphrase);
}

View File

@ -182,17 +182,17 @@ public class NfcOperationActivity extends BaseActivity {
case NFC_DECRYPT: case NFC_DECRYPT:
for (int i = 0; i < mNfcOperations.mInputHash.length; i++) { for (int i = 0; i < mNfcOperations.mInputHashes.length; i++) {
byte[] hash = mNfcOperations.mInputHash[i]; byte[] hash = mNfcOperations.mInputHashes[i];
byte[] decryptedSessionKey = nfcDecryptSessionKey(hash); byte[] decryptedSessionKey = nfcDecryptSessionKey(hash);
resultData.addCryptoData(hash, decryptedSessionKey); resultData.addCryptoData(hash, decryptedSessionKey);
} }
break; break;
case NFC_SIGN: case NFC_SIGN:
for (int i = 0; i < mNfcOperations.mInputHash.length; i++) { for (int i = 0; i < mNfcOperations.mInputHashes.length; i++) {
byte[] hash = mNfcOperations.mInputHash[i]; byte[] hash = mNfcOperations.mInputHashes[i];
int algo = mNfcOperations.mSignAlgo[i]; int algo = mNfcOperations.mSignAlgos[i];
byte[] signedHash = nfcCalculateSignature(hash, algo); byte[] signedHash = nfcCalculateSignature(hash, algo);
resultData.addCryptoData(hash, signedHash); resultData.addCryptoData(hash, signedHash);
} }

View File

@ -1099,9 +1099,9 @@
<string name="msg_crt_error_master_not_found">"Master key not found!"</string> <string name="msg_crt_error_master_not_found">"Master key not found!"</string>
<string name="msg_crt_error_nothing">"No keys certified!"</string> <string name="msg_crt_error_nothing">"No keys certified!"</string>
<string name="msg_crt_error_unlock">"Error unlocking master key!"</string> <string name="msg_crt_error_unlock">"Error unlocking master key!"</string>
<string name="msg_crt_error_divert">"Certification with NFC is not (yet) supported!"</string>
<string name="msg_crt">"Certifying keyrings"</string> <string name="msg_crt">"Certifying keyrings"</string>
<string name="msg_crt_master_fetch">"Fetching certifying master key"</string> <string name="msg_crt_master_fetch">"Fetching certifying master key"</string>
<string name="msg_crt_nfc_return">"Returning for NFC input"</string>
<string name="msg_crt_save">"Saving certified key %s"</string> <string name="msg_crt_save">"Saving certified key %s"</string>
<string name="msg_crt_saving">"Saving keyrings"</string> <string name="msg_crt_saving">"Saving keyrings"</string>
<string name="msg_crt_unlock">"Unlocking master key"</string> <string name="msg_crt_unlock">"Unlocking master key"</string>