certs: ditch expiry, re-add data blob, improve ViewCertActivity

GnuPG doesn't support expiry of user id certifications. The number
of rings with an expiration subpacket in a cert out there is likely
negligible.

ViewCertActivity now verifies the key and displays a status. For
revocation certs, the revocation reason is also shown.
This commit is contained in:
Vincent Breitmoser 2014-04-06 04:27:55 +02:00
parent f01a96f56e
commit 456a634149
6 changed files with 111 additions and 51 deletions

View File

@ -62,7 +62,7 @@ public class KeychainContract {
String TYPE = "type"; String TYPE = "type";
String VERIFIED = "verified"; String VERIFIED = "verified";
String CREATION = "creation"; String CREATION = "creation";
String EXPIRY = "expiry"; String DATA = "data";
} }
interface ApiAppsColumns { interface ApiAppsColumns {

View File

@ -114,7 +114,8 @@ public class KeychainDatabase extends SQLiteOpenHelper {
+ CertsColumns.TYPE + " INTEGER, " + CertsColumns.TYPE + " INTEGER, "
+ CertsColumns.VERIFIED + " INTEGER, " + CertsColumns.VERIFIED + " INTEGER, "
+ CertsColumns.CREATION + " INTEGER, " + CertsColumns.CREATION + " INTEGER, "
+ CertsColumns.EXPIRY + " INTEGER, "
+ CertsColumns.DATA + " BLOB, "
+ "PRIMARY KEY(" + CertsColumns.MASTER_KEY_ID + ", " + CertsColumns.RANK + ", " + "PRIMARY KEY(" + CertsColumns.MASTER_KEY_ID + ", " + CertsColumns.RANK + ", "
+ CertsColumns.KEY_ID_CERTIFIER + "), " + CertsColumns.KEY_ID_CERTIFIER + "), "

View File

@ -448,8 +448,8 @@ public class KeychainProvider extends ContentProvider {
projectionMap.put(Certs.VERIFIED, Tables.CERTS + "." + Certs.VERIFIED); projectionMap.put(Certs.VERIFIED, Tables.CERTS + "." + Certs.VERIFIED);
projectionMap.put(Certs.TYPE, Tables.CERTS + "." + Certs.TYPE); projectionMap.put(Certs.TYPE, Tables.CERTS + "." + Certs.TYPE);
projectionMap.put(Certs.CREATION, Tables.CERTS + "." + Certs.CREATION); projectionMap.put(Certs.CREATION, Tables.CERTS + "." + Certs.CREATION);
projectionMap.put(Certs.EXPIRY, Tables.CERTS + "." + Certs.EXPIRY);
projectionMap.put(Certs.KEY_ID_CERTIFIER, Tables.CERTS + "." + Certs.KEY_ID_CERTIFIER); projectionMap.put(Certs.KEY_ID_CERTIFIER, Tables.CERTS + "." + Certs.KEY_ID_CERTIFIER);
projectionMap.put(Certs.DATA, Tables.CERTS + "." + Certs.DATA);
projectionMap.put(Certs.USER_ID, Tables.USER_IDS + "." + UserIds.USER_ID); projectionMap.put(Certs.USER_ID, Tables.USER_IDS + "." + UserIds.USER_ID);
projectionMap.put(Certs.SIGNER_UID, "signer." + UserIds.USER_ID + " AS " + Certs.SIGNER_UID); projectionMap.put(Certs.SIGNER_UID, "signer." + UserIds.USER_ID + " AS " + Certs.SIGNER_UID);
qb.setProjectionMap(projectionMap); qb.setProjectionMap(projectionMap);

View File

@ -246,10 +246,8 @@ public class ProviderHelper {
try { try {
// self signature // self signature
if(certId == masterKeyId) { if(certId == masterKeyId) {
cert.init( cert.init(new JcaPGPContentVerifierBuilderProvider().setProvider(
new JcaPGPContentVerifierBuilderProvider().setProvider( Constants.BOUNCY_CASTLE_PROVIDER_NAME), masterKey);
Constants.BOUNCY_CASTLE_PROVIDER_NAME),
masterKey);
if(!cert.verifyCertification(userId, masterKey)) { if(!cert.verifyCertification(userId, masterKey)) {
// not verified?! dang! TODO notify user? this is kinda serious... // not verified?! dang! TODO notify user? this is kinda serious...
Log.e(Constants.TAG, "Could not verify self signature for " + userId + "!"); Log.e(Constants.TAG, "Could not verify self signature for " + userId + "!");
@ -267,8 +265,7 @@ public class ProviderHelper {
// verify signatures from known private keys // verify signatures from known private keys
if(allKeyRings.containsKey(certId)) { if(allKeyRings.containsKey(certId)) {
// mark them as verified // mark them as verified
cert.init( cert.init(new JcaPGPContentVerifierBuilderProvider().setProvider(
new JcaPGPContentVerifierBuilderProvider().setProvider(
Constants.BOUNCY_CASTLE_PROVIDER_NAME), Constants.BOUNCY_CASTLE_PROVIDER_NAME),
allKeyRings.get(certId).getPublicKey()); allKeyRings.get(certId).getPublicKey());
if(cert.verifyCertification(userId, masterKey)) { if(cert.verifyCertification(userId, masterKey)) {
@ -423,12 +420,8 @@ public class ProviderHelper {
values.put(Certs.KEY_ID_CERTIFIER, cert.getKeyID()); values.put(Certs.KEY_ID_CERTIFIER, cert.getKeyID());
values.put(Certs.TYPE, cert.getSignatureType()); values.put(Certs.TYPE, cert.getSignatureType());
values.put(Certs.CREATION, cert.getCreationTime().getTime() / 1000); values.put(Certs.CREATION, cert.getCreationTime().getTime() / 1000);
if(cert.getHashedSubPackets().hasSubpacket(SignatureSubpacketTags.EXPIRE_TIME)) {
long ext = ((SignatureExpirationTime) cert.getHashedSubPackets().getSubpacket(
SignatureSubpacketTags.EXPIRE_TIME)).getTime();
values.put(Certs.EXPIRY, cert.getCreationTime().getTime() / 1000 + ext);
}
values.put(Certs.VERIFIED, verified); values.put(Certs.VERIFIED, verified);
values.put(Certs.DATA, cert.getEncoded());
Uri uri = Certs.buildCertsUri(Long.toString(masterKeyId)); Uri uri = Certs.buildCertsUri(Long.toString(masterKeyId));

View File

@ -30,18 +30,27 @@ import android.support.v7.app.ActionBarActivity;
import android.text.format.DateFormat; import android.text.format.DateFormat;
import android.view.Menu; import android.view.Menu;
import android.view.MenuItem; import android.view.MenuItem;
import android.view.View;
import android.widget.TextView; import android.widget.TextView;
import org.spongycastle.bcpg.SignatureSubpacket;
import org.spongycastle.bcpg.SignatureSubpacketTags;
import org.spongycastle.bcpg.sig.RevocationReason;
import org.spongycastle.openpgp.PGPException;
import org.spongycastle.openpgp.PGPKeyRing;
import org.spongycastle.openpgp.PGPSignature; 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.R; import org.sufficientlysecure.keychain.R;
import org.sufficientlysecure.keychain.pgp.PgpConversionHelper; import org.sufficientlysecure.keychain.pgp.PgpConversionHelper;
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.KeychainContract.Certs; import org.sufficientlysecure.keychain.provider.KeychainContract.Certs;
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 java.security.SignatureException;
import java.util.Date; import java.util.Date;
/** /**
@ -56,24 +65,25 @@ public class ViewCertActivity extends ActionBarActivity
Certs.USER_ID, Certs.USER_ID,
Certs.TYPE, Certs.TYPE,
Certs.CREATION, Certs.CREATION,
Certs.EXPIRY,
Certs.KEY_ID_CERTIFIER, Certs.KEY_ID_CERTIFIER,
Certs.SIGNER_UID, Certs.SIGNER_UID,
Certs.DATA,
}; };
private static final int INDEX_MASTER_KEY_ID = 0; private static final int INDEX_MASTER_KEY_ID = 0;
private static final int INDEX_USER_ID = 1; private static final int INDEX_USER_ID = 1;
private static final int INDEX_TYPE = 2; private static final int INDEX_TYPE = 2;
private static final int INDEX_CREATION = 3; private static final int INDEX_CREATION = 3;
private static final int INDEX_EXPIRY = 4; private static final int INDEX_KEY_ID_CERTIFIER = 4;
private static final int INDEX_KEY_ID_CERTIFIER = 5; private static final int INDEX_SIGNER_UID = 5;
private static final int INDEX_SIGNER_UID = 6; private static final int INDEX_DATA = 6;
private Uri mDataUri; private Uri mDataUri;
private long mSignerKeyId; private long mSignerKeyId;
private TextView mSigneeKey, mSigneeUid, mRank, mAlgorithm, mType, mCreation, mExpiry; private TextView mSigneeKey, mSigneeUid, mAlgorithm, mType, mRReason, mCreation;
private TextView mSignerKey, mSignerUid; private TextView mSignerKey, mSignerUid, mStatus;
private View mRowReason;
@Override @Override
protected void onCreate(Bundle savedInstanceState) { protected void onCreate(Bundle savedInstanceState) {
@ -84,16 +94,19 @@ public class ViewCertActivity extends ActionBarActivity
setContentView(R.layout.view_cert_activity); setContentView(R.layout.view_cert_activity);
mStatus = (TextView) findViewById(R.id.status);
mSigneeKey = (TextView) findViewById(R.id.signee_key); mSigneeKey = (TextView) findViewById(R.id.signee_key);
mSigneeUid = (TextView) findViewById(R.id.signee_uid); mSigneeUid = (TextView) findViewById(R.id.signee_uid);
mAlgorithm = (TextView) findViewById(R.id.algorithm); mAlgorithm = (TextView) findViewById(R.id.algorithm);
mType = (TextView) findViewById(R.id.signature_type); mType = (TextView) findViewById(R.id.signature_type);
mRReason = (TextView) findViewById(R.id.reason);
mCreation = (TextView) findViewById(R.id.creation); mCreation = (TextView) findViewById(R.id.creation);
mExpiry = (TextView) findViewById(R.id.expiry);
mSignerKey = (TextView) findViewById(R.id.signer_key_id); mSignerKey = (TextView) findViewById(R.id.signer_key_id);
mSignerUid = (TextView) findViewById(R.id.signer_uid); mSignerUid = (TextView) findViewById(R.id.signer_uid);
mRowReason = findViewById(R.id.row_reason);
mDataUri = getIntent().getData(); mDataUri = getIntent().getData();
if (mDataUri == null) { if (mDataUri == null) {
Log.e(Constants.TAG, "Intent data missing. Should be Uri of key!"); Log.e(Constants.TAG, "Intent data missing. Should be Uri of key!");
@ -134,9 +147,36 @@ public class ViewCertActivity extends ActionBarActivity
else else
mSignerUid.setText(R.string.unknown_uid); mSignerUid.setText(R.string.unknown_uid);
// String algorithmStr = PgpKeyHelper.getAlgorithmInfo(sig.getKeyAlgorithm(), 0); PGPSignature sig = PgpConversionHelper.BytesToPGPSignature(data.getBlob(INDEX_DATA));
// mAlgorithm.setText(algorithmStr); PGPKeyRing signeeRing = ProviderHelper.getPGPKeyRing(this,
KeychainContract.KeyRingData.buildPublicKeyRingUri(Long.toString(data.getLong(INDEX_MASTER_KEY_ID))));
PGPKeyRing signerRing = ProviderHelper.getPGPKeyRing(this,
KeychainContract.KeyRingData.buildPublicKeyRingUri(Long.toString(sig.getKeyID())));
if(signerRing != null) try {
sig.init(new JcaPGPContentVerifierBuilderProvider().setProvider(
Constants.BOUNCY_CASTLE_PROVIDER_NAME), signeeRing.getPublicKey());
if (sig.verifyCertification(signeeUid, signerRing.getPublicKey())) {
mStatus.setText("ok");
mStatus.setTextColor(getResources().getColor(R.color.bbutton_success));
} else {
mStatus.setText("failed!");
mStatus.setTextColor(getResources().getColor(R.color.alert));
}
} catch(SignatureException e) {
mStatus.setText("error!");
mStatus.setTextColor(getResources().getColor(R.color.alert));
} catch(PGPException e) {
mStatus.setText("error!");
mStatus.setTextColor(getResources().getColor(R.color.alert));
} else {
mStatus.setText("key unavailable");
mStatus.setTextColor(getResources().getColor(R.color.black));
}
String algorithmStr = PgpKeyHelper.getAlgorithmInfo(sig.getKeyAlgorithm(), 0);
mAlgorithm.setText(algorithmStr);
mRowReason.setVisibility(View.GONE);
switch(data.getInt(INDEX_TYPE)) { switch(data.getInt(INDEX_TYPE)) {
case PGPSignature.DEFAULT_CERTIFICATION: case PGPSignature.DEFAULT_CERTIFICATION:
mType.setText(R.string.cert_default); break; mType.setText(R.string.cert_default); break;
@ -146,16 +186,21 @@ public class ViewCertActivity extends ActionBarActivity
mType.setText(R.string.cert_casual); break; mType.setText(R.string.cert_casual); break;
case PGPSignature.POSITIVE_CERTIFICATION: case PGPSignature.POSITIVE_CERTIFICATION:
mType.setText(R.string.cert_positive); break; mType.setText(R.string.cert_positive); break;
case PGPSignature.CERTIFICATION_REVOCATION: case PGPSignature.CERTIFICATION_REVOCATION: {
mType.setText(R.string.cert_revoke); break; mType.setText(R.string.cert_revoke);
} if(sig.getHashedSubPackets().hasSubpacket(SignatureSubpacketTags.REVOCATION_REASON)) {
SignatureSubpacket p = sig.getHashedSubPackets().getSubpacket(
long expiry = data.getLong(INDEX_EXPIRY); SignatureSubpacketTags.REVOCATION_REASON);
if(expiry == 0) { // For some reason, this is missing in SignatureSubpacketInputStream:146
mExpiry.setText(R.string.never); if(!(p instanceof RevocationReason)) {
} else { p = new RevocationReason(false, p.getData());
Date expiryDate = new Date(creationDate.getTime() + expiry * 1000); }
mExpiry.setText(DateFormat.getDateFormat(getApplicationContext()).format(expiryDate)); String reason = ((RevocationReason) p).getRevocationDescription();
mRReason.setText(reason);
mRowReason.setVisibility(View.VISIBLE);
}
break;
}
} }
} }
} }

View File

@ -14,6 +14,26 @@
android:paddingLeft="16dp" android:paddingLeft="16dp"
android:paddingRight="16dp"> android:paddingRight="16dp">
<LinearLayout
android:orientation="horizontal"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="center"
android:layout_marginTop="14dp">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Verification Status" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="ok"
android:id="@+id/status"
android:layout_marginLeft="30dp" />
</LinearLayout>
<TextView <TextView
style="@style/SectionHeader" style="@style/SectionHeader"
android:layout_width="wrap_content" android:layout_width="wrap_content"
@ -96,6 +116,25 @@
android:paddingRight="5dip" /> android:paddingRight="5dip" />
</TableRow> </TableRow>
<TableRow
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:id="@+id/row_reason">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="top"
android:paddingRight="10dip"
android:text="Revocation Reason" />
<TextView
android:id="@+id/reason"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:paddingRight="5dip" />
</TableRow>
<TableRow> <TableRow>
<TextView <TextView
@ -112,24 +151,6 @@
android:paddingRight="5dip" /> android:paddingRight="5dip" />
</TableRow> </TableRow>
<TableRow
android:layout_width="fill_parent"
android:layout_height="fill_parent">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="top"
android:paddingRight="10dip"
android:text="Expires" />
<TextView
android:id="@+id/expiry"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:paddingRight="5dip" />
</TableRow>
</TableLayout> </TableLayout>
<TextView <TextView