mirror of
https://github.com/moparisthebest/open-keychain
synced 2024-11-23 17:22:16 -05:00
add support for user attributes (during canonicalization)
This commit is contained in:
parent
01c3e7bbf9
commit
fc3397de5d
@ -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
|
||||
|
@ -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;
|
||||
|
@ -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);
|
||||
}
|
||||
|
@ -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";
|
||||
}
|
||||
|
||||
}
|
@ -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>
|
||||
|
Loading…
Reference in New Issue
Block a user