add support for user attributes (during canonicalization)

This commit is contained in:
Vincent Breitmoser 2015-01-12 20:03:03 +01:00
parent 01c3e7bbf9
commit fc3397de5d
5 changed files with 366 additions and 1 deletions

View File

@ -416,6 +416,21 @@ public abstract class OperationResult implements Parcelable {
MSG_KC_UID_REVOKE_OLD (LogLevel.DEBUG, R.string.msg_kc_uid_revoke_old),
MSG_KC_UID_REMOVE (LogLevel.DEBUG, R.string.msg_kc_uid_remove),
MSG_KC_UID_WARN_ENCODING (LogLevel.WARN, R.string.msg_kc_uid_warn_encoding),
MSG_KC_UAT_JPEG (LogLevel.DEBUG, R.string.msg_kc_uat_jpeg),
MSG_KC_UAT_UNKNOWN (LogLevel.DEBUG, R.string.msg_kc_uat_unknown),
MSG_KC_UAT_BAD_ERR (LogLevel.WARN, R.string.msg_kc_uat_bad_err),
MSG_KC_UAT_BAD_LOCAL (LogLevel.WARN, R.string.msg_kc_uat_bad_local),
MSG_KC_UAT_BAD_TIME (LogLevel.WARN, R.string.msg_kc_uat_bad_time),
MSG_KC_UAT_BAD_TYPE (LogLevel.WARN, R.string.msg_kc_uat_bad_type),
MSG_KC_UAT_BAD (LogLevel.WARN, R.string.msg_kc_uat_bad),
MSG_KC_UAT_CERT_DUP (LogLevel.DEBUG, R.string.msg_kc_uat_cert_dup),
MSG_KC_UAT_DUP (LogLevel.DEBUG, R.string.msg_kc_uat_dup),
MSG_KC_UAT_FOREIGN (LogLevel.DEBUG, R.string.msg_kc_uat_foreign),
MSG_KC_UAT_NO_CERT (LogLevel.DEBUG, R.string.msg_kc_uat_no_cert),
MSG_KC_UAT_REVOKE_DUP (LogLevel.DEBUG, R.string.msg_kc_uat_revoke_dup),
MSG_KC_UAT_REVOKE_OLD (LogLevel.DEBUG, R.string.msg_kc_uat_revoke_old),
MSG_KC_UAT_REMOVE (LogLevel.DEBUG, R.string.msg_kc_uat_remove),
MSG_KC_UAT_WARN_ENCODING (LogLevel.WARN, R.string.msg_kc_uat_warn_encoding),
// keyring consolidation

View File

@ -21,6 +21,7 @@ package org.sufficientlysecure.keychain.pgp;
import org.spongycastle.bcpg.ArmoredOutputStream;
import org.spongycastle.bcpg.PublicKeyAlgorithmTags;
import org.spongycastle.bcpg.SignatureSubpacketTags;
import org.spongycastle.bcpg.UserAttributeSubpacketTags;
import org.spongycastle.bcpg.sig.KeyFlags;
import org.spongycastle.openpgp.PGPKeyRing;
import org.spongycastle.openpgp.PGPObjectFactory;
@ -30,6 +31,7 @@ import org.spongycastle.openpgp.PGPSecretKey;
import org.spongycastle.openpgp.PGPSecretKeyRing;
import org.spongycastle.openpgp.PGPSignature;
import org.spongycastle.openpgp.PGPSignatureList;
import org.spongycastle.openpgp.PGPUserAttributeSubpacketVector;
import org.spongycastle.openpgp.PGPUtil;
import org.spongycastle.openpgp.operator.jcajce.JcaKeyFingerprintCalculator;
import org.sufficientlysecure.keychain.Constants;
@ -605,6 +607,170 @@ public class UncachedKeyRing {
return null;
}
ArrayList<PGPUserAttributeSubpacketVector> processedUserAttributes = new ArrayList<>();
for (PGPUserAttributeSubpacketVector userAttribute :
new IterableIterator<PGPUserAttributeSubpacketVector>(masterKey.getUserAttributes())) {
if (userAttribute.getSubpacket(UserAttributeSubpacketTags.IMAGE_ATTRIBUTE) != null) {
log.add(LogType.MSG_KC_UAT_JPEG, indent);
} else {
log.add(LogType.MSG_KC_UAT_UNKNOWN, indent);
}
try {
indent += 1;
// check for duplicate user attributes
if (processedUserAttributes.contains(userAttribute)) {
log.add(LogType.MSG_KC_UAT_DUP, indent);
// strip out the first found user id with this name
modified = PGPPublicKey.removeCertification(modified, userAttribute);
}
processedUserAttributes.add(userAttribute);
PGPSignature selfCert = null;
revocation = null;
// look through signatures for this specific user id
@SuppressWarnings("unchecked")
Iterator<PGPSignature> signaturesIt = masterKey.getSignaturesForUserAttribute(userAttribute);
if (signaturesIt != null) {
for (PGPSignature zert : new IterableIterator<PGPSignature>(signaturesIt)) {
WrappedSignature cert = new WrappedSignature(zert);
long certId = cert.getKeyId();
int type = zert.getSignatureType();
if (type != PGPSignature.DEFAULT_CERTIFICATION
&& type != PGPSignature.NO_CERTIFICATION
&& type != PGPSignature.CASUAL_CERTIFICATION
&& type != PGPSignature.POSITIVE_CERTIFICATION
&& type != PGPSignature.CERTIFICATION_REVOCATION) {
log.add(LogType.MSG_KC_UAT_BAD_TYPE,
indent, "0x" + Integer.toString(zert.getSignatureType(), 16));
modified = PGPPublicKey.removeCertification(modified, userAttribute, zert);
badCerts += 1;
continue;
}
if (cert.getCreationTime().after(nowPlusOneDay)) {
// Creation date in the future? No way!
log.add(LogType.MSG_KC_UAT_BAD_TIME, indent);
modified = PGPPublicKey.removeCertification(modified, userAttribute, zert);
badCerts += 1;
continue;
}
if (cert.isLocal()) {
// Creation date in the future? No way!
log.add(LogType.MSG_KC_UAT_BAD_LOCAL, indent);
modified = PGPPublicKey.removeCertification(modified, userAttribute, zert);
badCerts += 1;
continue;
}
// If this is a foreign signature, ...
if (certId != masterKeyId) {
// never mind any further for public keys, but remove them from secret ones
if (isSecret()) {
log.add(LogType.MSG_KC_UAT_FOREIGN,
indent, KeyFormattingUtils.convertKeyIdToHex(certId));
modified = PGPPublicKey.removeCertification(modified, userAttribute, zert);
badCerts += 1;
}
continue;
}
// Otherwise, first make sure it checks out
try {
cert.init(masterKey);
if (!cert.verifySignature(masterKey, userAttribute)) {
log.add(LogType.MSG_KC_UAT_BAD,
indent);
modified = PGPPublicKey.removeCertification(modified, userAttribute, zert);
badCerts += 1;
continue;
}
} catch (PgpGeneralException e) {
log.add(LogType.MSG_KC_UAT_BAD_ERR,
indent);
modified = PGPPublicKey.removeCertification(modified, userAttribute, zert);
badCerts += 1;
continue;
}
switch (type) {
case PGPSignature.DEFAULT_CERTIFICATION:
case PGPSignature.NO_CERTIFICATION:
case PGPSignature.CASUAL_CERTIFICATION:
case PGPSignature.POSITIVE_CERTIFICATION:
if (selfCert == null) {
selfCert = zert;
} else if (selfCert.getCreationTime().before(cert.getCreationTime())) {
log.add(LogType.MSG_KC_UAT_CERT_DUP,
indent);
modified = PGPPublicKey.removeCertification(modified, userAttribute, selfCert);
redundantCerts += 1;
selfCert = zert;
} else {
log.add(LogType.MSG_KC_UAT_CERT_DUP,
indent);
modified = PGPPublicKey.removeCertification(modified, userAttribute, zert);
redundantCerts += 1;
}
// If there is a revocation certificate, and it's older than this, drop it
if (revocation != null
&& revocation.getCreationTime().before(selfCert.getCreationTime())) {
log.add(LogType.MSG_KC_UAT_REVOKE_OLD,
indent);
modified = PGPPublicKey.removeCertification(modified, userAttribute, revocation);
revocation = null;
redundantCerts += 1;
}
break;
case PGPSignature.CERTIFICATION_REVOCATION:
// If this is older than the (latest) self cert, drop it
if (selfCert != null && selfCert.getCreationTime().after(zert.getCreationTime())) {
log.add(LogType.MSG_KC_UAT_REVOKE_OLD,
indent);
modified = PGPPublicKey.removeCertification(modified, userAttribute, zert);
redundantCerts += 1;
continue;
}
// first revocation? remember it.
if (revocation == null) {
revocation = zert;
// more revocations? at least one is superfluous, then.
} else if (revocation.getCreationTime().before(cert.getCreationTime())) {
log.add(LogType.MSG_KC_UAT_REVOKE_DUP,
indent);
modified = PGPPublicKey.removeCertification(modified, userAttribute, revocation);
redundantCerts += 1;
revocation = zert;
} else {
log.add(LogType.MSG_KC_UAT_REVOKE_DUP,
indent);
modified = PGPPublicKey.removeCertification(modified, userAttribute, zert);
redundantCerts += 1;
}
break;
}
}
}
// If no valid certificate (if only a revocation) remains, drop it
if (selfCert == null && revocation == null) {
log.add(LogType.MSG_KC_UAT_REMOVE,
indent);
modified = PGPPublicKey.removeCertification(modified, userAttribute);
}
} finally {
indent -= 1;
}
}
// Replace modified key in the keyring
ring = replacePublicKey(ring, modified);
indent -= 1;

View File

@ -29,6 +29,7 @@ import org.spongycastle.openpgp.PGPObjectFactory;
import org.spongycastle.openpgp.PGPPublicKey;
import org.spongycastle.openpgp.PGPSignature;
import org.spongycastle.openpgp.PGPSignatureList;
import org.spongycastle.openpgp.PGPUserAttributeSubpacketVector;
import org.spongycastle.openpgp.operator.jcajce.JcaPGPContentVerifierBuilderProvider;
import org.sufficientlysecure.keychain.Constants;
import org.sufficientlysecure.keychain.pgp.exception.PgpGeneralException;
@ -199,6 +200,14 @@ public class WrappedSignature {
}
}
boolean verifySignature(PGPPublicKey key, PGPUserAttributeSubpacketVector attribute) throws PgpGeneralException {
try {
return mSig.verifyCertification(attribute, key);
} catch (PGPException e) {
throw new PgpGeneralException("Error!", e);
}
}
public boolean verifySignature(UncachedPublicKey key, byte[] rawUserId) throws PgpGeneralException {
return verifySignature(key.getPublicKey(), rawUserId);
}

View File

@ -0,0 +1,160 @@
package org.sufficientlysecure.keychain.pgp.affirmation;
import org.spongycastle.bcpg.UserAttributeSubpacket;
import org.spongycastle.util.Strings;
import org.spongycastle.util.encoders.Hex;
import org.sufficientlysecure.keychain.Constants;
import org.sufficientlysecure.keychain.util.Log;
import java.net.URI;
import java.util.Arrays;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Map.Entry;
import java.util.Set;
public class LinkedIdentity {
protected byte[] mData;
public final String mNonce;
public final URI mSubUri;
final Set<String> mFlags;
final HashMap<String,String> mParams;
protected LinkedIdentity(byte[] data, String nonce, Set<String> flags,
HashMap<String, String> params, URI subUri) {
if ( ! nonce.matches("[0-9a-zA-Z]+")) {
throw new AssertionError("bug: nonce must be hexstring!");
}
mData = data;
mNonce = nonce;
mFlags = flags;
mParams = params;
mSubUri = subUri;
}
LinkedIdentity(String nonce, Set<String> flags,
HashMap<String, String> params, URI subUri) {
this(null, nonce, flags, params, subUri);
}
public byte[] encode() {
if (mData != null) {
return mData;
}
StringBuilder b = new StringBuilder();
b.append("pgpid:");
// add flags
if (mFlags != null) {
boolean first = true;
for (String flag : mFlags) {
if (!first) {
b.append(";");
}
first = false;
b.append(flag);
}
}
// add parameters
if (mParams != null) {
boolean first = true;
Iterator<Entry<String, String>> it = mParams.entrySet().iterator();
while (it.hasNext()) {
if (!first) {
b.append(";");
}
first = false;
Entry<String, String> entry = it.next();
b.append(entry.getKey()).append("=").append(entry.getValue());
}
}
b.append("@");
b.append(mSubUri);
byte[] nonceBytes = Hex.decode(mNonce);
byte[] data = Strings.toUTF8ByteArray(b.toString());
byte[] result = new byte[data.length+12];
System.arraycopy(nonceBytes, 0, result, 0, 12);
System.arraycopy(data, 0, result, 12, result.length);
return result;
}
/** This method parses an affirmation from a UserAttributeSubpacket, or returns null if the
* subpacket can not be parsed as a valid affirmation.
*/
public static LinkedIdentity parseAffirmation(UserAttributeSubpacket subpacket) {
if (subpacket.getType() != 100) {
return null;
}
byte[] data = subpacket.getData();
String nonce = Hex.toHexString(data, 0, 12);
try {
return parseUri(nonce, Strings.fromUTF8ByteArray(Arrays.copyOfRange(data, 12, data.length)));
} catch (IllegalArgumentException e) {
Log.e(Constants.TAG, "error parsing uri in (suspected) affirmation packet");
return null;
}
}
protected static LinkedIdentity parseUri (String nonce, String uriString) {
URI uri = URI.create(uriString);
if ("pgpid".equals(uri.getScheme())) {
Log.e(Constants.TAG, "unknown uri scheme in (suspected) affirmation packet");
return null;
}
if (!uri.isOpaque()) {
Log.e(Constants.TAG, "non-opaque uri in (suspected) affirmation packet");
return null;
}
String specific = uri.getSchemeSpecificPart();
if (!specific.contains("@")) {
Log.e(Constants.TAG, "unknown uri scheme in affirmation packet");
return null;
}
String[] pieces = specific.split("@", 2);
URI subUri = URI.create(pieces[1]);
Set<String> flags = new HashSet<String>();
HashMap<String,String> params = new HashMap<String,String>();
{
String[] rawParams = pieces[0].split(";");
for (String param : rawParams) {
String[] p = param.split("=", 2);
if (p.length == 1) {
flags.add(param);
} else {
params.put(p[0], p[1]);
}
}
}
return new LinkedIdentity(nonce, flags, params, subUri);
}
public static String generateNonce() {
// TODO make this actually random
// byte[] data = new byte[96];
// new SecureRandom().nextBytes(data);
// return Hex.toHexString(data);
// debug for now
return "0123456789ABCDEF01234567";
}
}

View File

@ -760,8 +760,23 @@
<string name="msg_kc_uid_revoke_old">"Removing outdated revocation certificate for user ID '%s'"</string>
<string name="msg_kc_uid_no_cert">"No valid self-certificate found for user ID '%s', removing from ring"</string>
<string name="msg_kc_uid_remove">"Removing invalid user ID '%s'"</string>
<string name="msg_kc_uid_dup">"Removing duplicate user ID '%s'. The secret key contained two of them. This may result in missing certificates!"</string>
<string name="msg_kc_uid_dup">"Removing duplicate user ID '%s'. The keyring contained two of them. This may result in missing certificates!"</string>
<string name="msg_kc_uid_warn_encoding">"User id does not verify as UTF-8!"</string>
<string name="msg_kc_uat_jpeg">"Processing user attribute of type JPEG"</string>
<string name="msg_kc_uat_unknown">"Processing user attribute of unknown type"</string>
<string name="msg_kc_uat_bad_err">"Removing bad self certificate for user attribute"</string>
<string name="msg_kc_uat_bad_local">"Removing user attribute certificate with 'local' flag"</string>
<string name="msg_kc_uat_bad_time">"Removing user attribute with future timestamp"</string>
<string name="msg_kc_uat_bad_type">"Removing user attribute certificate of unknown type (%s)"</string>
<string name="msg_kc_uat_bad">"Removing bad self certificate for user attribute"</string>
<string name="msg_kc_uat_cert_dup">"Removing outdated self certificate for user attribute"</string>
<string name="msg_kc_uat_dup">"Removing duplicate user attribute. The keyring contained two of them. This may result in missing certificates!"</string>
<string name="msg_kc_uat_foreign">"Removing foreign user attribute certificate by"</string>
<string name="msg_kc_uat_revoke_dup">"Removing redundant revocation certificate for user attribute"</string>
<string name="msg_kc_uat_revoke_old">"Removing outdated revocation certificate for user attribute"</string>
<string name="msg_kc_uat_no_cert">"No valid self-certificate found for user attribute, removing from ring"</string>
<string name="msg_kc_uat_remove">"Removing invalid user attribute"</string>
<string name="msg_kc_uat_warn_encoding">"User id does not verify as UTF-8!"</string>
<!-- Keyring merging log entries -->
<string name="msg_mg_error_secret_dummy">"New public subkey found, but secret subkey dummy generation is not supported!"</string>