add ecc support

This commit is contained in:
Vincent Breitmoser
2014-08-24 05:12:09 +02:00
parent 08bdb0c5b9
commit 783dae8804
26 changed files with 524 additions and 191 deletions

View File

@@ -24,10 +24,16 @@ import android.text.Spannable;
import android.text.SpannableStringBuilder;
import android.text.style.ForegroundColorSpan;
import org.spongycastle.asn1.ASN1ObjectIdentifier;
import org.spongycastle.asn1.nist.NISTNamedCurves;
import org.spongycastle.asn1.teletrust.TeleTrusTNamedCurves;
import org.spongycastle.bcpg.ECPublicBCPGKey;
import org.spongycastle.bcpg.PublicKeyAlgorithmTags;
import org.spongycastle.util.encoders.Hex;
import org.sufficientlysecure.keychain.Constants;
import org.sufficientlysecure.keychain.R;
import org.sufficientlysecure.keychain.service.SaveKeyringParcel.Algorithm;
import org.sufficientlysecure.keychain.service.SaveKeyringParcel.Curve;
import org.sufficientlysecure.keychain.util.Log;
import java.security.DigestException;
@@ -37,18 +43,14 @@ import java.util.Locale;
public class PgpKeyHelper {
public static String getAlgorithmInfo(int algorithm) {
return getAlgorithmInfo(null, algorithm, 0);
}
public static String getAlgorithmInfo(Context context, int algorithm) {
return getAlgorithmInfo(context, algorithm, 0);
public static String getAlgorithmInfo(int algorithm, Integer keySize, String oid) {
return getAlgorithmInfo(null, algorithm, keySize, oid);
}
/**
* Based on <a href="http://tools.ietf.org/html/rfc2440#section-9.1">OpenPGP Message Format</a>
*/
public static String getAlgorithmInfo(Context context, int algorithm, int keySize) {
public static String getAlgorithmInfo(Context context, int algorithm, Integer keySize, String oid) {
String algorithmStr;
switch (algorithm) {
@@ -69,10 +71,19 @@ public class PgpKeyHelper {
break;
}
case PublicKeyAlgorithmTags.ECDSA:
case PublicKeyAlgorithmTags.ECDSA: {
if (oid == null) {
return "ECDSA";
}
String oidName = PgpKeyHelper.getCurveInfo(context, oid);
return "ECDSA (" + oidName + ")";
}
case PublicKeyAlgorithmTags.ECDH: {
algorithmStr = "ECC";
break;
if (oid == null) {
return "ECDH";
}
String oidName = PgpKeyHelper.getCurveInfo(context, oid);
return "ECDH (" + oidName + ")";
}
default: {
@@ -90,6 +101,106 @@ public class PgpKeyHelper {
return algorithmStr;
}
public static String getAlgorithmInfo(Algorithm algorithm, Integer keySize, Curve curve) {
return getAlgorithmInfo(null, algorithm, keySize, curve);
}
/**
* Based on <a href="http://tools.ietf.org/html/rfc2440#section-9.1">OpenPGP Message Format</a>
*/
public static String getAlgorithmInfo(Context context, Algorithm algorithm, Integer keySize, Curve curve) {
String algorithmStr;
switch (algorithm) {
case RSA: {
algorithmStr = "RSA";
break;
}
case DSA: {
algorithmStr = "DSA";
break;
}
case ELGAMAL: {
algorithmStr = "ElGamal";
break;
}
case ECDSA: {
algorithmStr = "ECDSA";
if (curve != null) {
algorithmStr += " (" + getCurveInfo(context, curve) + ")";
}
return algorithmStr;
}
case ECDH: {
algorithmStr = "ECDH";
if (curve != null) {
algorithmStr += " (" + getCurveInfo(context, curve) + ")";
}
return algorithmStr;
}
default: {
if (context != null) {
algorithmStr = context.getResources().getString(R.string.unknown_algorithm);
} else {
algorithmStr = "unknown";
}
break;
}
}
if (keySize != null && keySize > 0)
return algorithmStr + ", " + keySize + " bit";
else
return algorithmStr;
}
// Return name of a curve. These are names, no need for translation
public static String getCurveInfo(Context context, Curve curve) {
switch(curve) {
case NIST_P256:
return "NIST P-256";
case NIST_P384:
return "NIST P-384";
case NIST_P521:
return "NIST P-521";
/* see SaveKeyringParcel
case BRAINPOOL_P256:
return "Brainpool P-256";
case BRAINPOOL_P384:
return "Brainpool P-384";
case BRAINPOOL_P512:
return "Brainpool P-512";
*/
}
if (context != null) {
return context.getResources().getString(R.string.unknown_algorithm);
} else {
return "unknown";
}
}
public static String getCurveInfo(Context context, String oidStr) {
ASN1ObjectIdentifier oid = new ASN1ObjectIdentifier(oidStr);
String name;
name = NISTNamedCurves.getName(oid);
if (name != null) {
return name;
}
name = TeleTrusTNamedCurves.getName(oid);
if (name != null) {
return name;
}
if (context != null) {
return context.getResources().getString(R.string.unknown_algorithm);
} else {
return "unknown";
}
}
/**
* Converts fingerprint to hex (optional: with whitespaces after 4 characters)
* <p/>

View File

@@ -20,12 +20,12 @@ package org.sufficientlysecure.keychain.pgp;
import org.spongycastle.bcpg.CompressionAlgorithmTags;
import org.spongycastle.bcpg.HashAlgorithmTags;
import org.spongycastle.bcpg.PublicKeyAlgorithmTags;
import org.spongycastle.bcpg.SymmetricKeyAlgorithmTags;
import org.spongycastle.bcpg.sig.Features;
import org.spongycastle.bcpg.sig.KeyFlags;
import org.spongycastle.jce.spec.ElGamalParameterSpec;
import org.spongycastle.openpgp.PGPException;
import org.spongycastle.openpgp.PGPKeyFlags;
import org.spongycastle.openpgp.PGPKeyPair;
import org.spongycastle.openpgp.PGPPrivateKey;
import org.spongycastle.openpgp.PGPPublicKey;
@@ -52,6 +52,8 @@ import org.sufficientlysecure.keychain.service.OperationResultParcel.LogType;
import org.sufficientlysecure.keychain.service.OperationResultParcel.OperationLog;
import org.sufficientlysecure.keychain.service.OperationResults.EditKeyResult;
import org.sufficientlysecure.keychain.service.SaveKeyringParcel;
import org.sufficientlysecure.keychain.service.SaveKeyringParcel.Algorithm;
import org.sufficientlysecure.keychain.service.SaveKeyringParcel.Curve;
import org.sufficientlysecure.keychain.service.SaveKeyringParcel.SubkeyAdd;
import org.sufficientlysecure.keychain.util.IterableIterator;
import org.sufficientlysecure.keychain.util.Log;
@@ -66,6 +68,7 @@ import java.security.NoSuchAlgorithmException;
import java.security.NoSuchProviderException;
import java.security.SecureRandom;
import java.security.SignatureException;
import java.security.spec.ECGenParameterSpec;
import java.util.Arrays;
import java.util.Date;
import java.util.Iterator;
@@ -155,31 +158,65 @@ public class PgpKeyOperation {
mProgress.peek().setProgress(message, current, 100);
}
private ECGenParameterSpec getEccParameterSpec(Curve curve) {
switch (curve) {
case NIST_P256: return new ECGenParameterSpec("P-256");
case NIST_P384: return new ECGenParameterSpec("P-384");
case NIST_P521: return new ECGenParameterSpec("P-521");
// @see SaveKeyringParcel
// case BRAINPOOL_P256: return new ECGenParameterSpec("brainpoolp256r1");
// case BRAINPOOL_P384: return new ECGenParameterSpec("brainpoolp384r1");
// case BRAINPOOL_P512: return new ECGenParameterSpec("brainpoolp512r1");
}
throw new RuntimeException("Invalid choice! (can't happen)");
}
/** Creates new secret key. */
private PGPKeyPair createKey(int algorithmChoice, int keySize, OperationLog log, int indent) {
private PGPKeyPair createKey(SubkeyAdd add, OperationLog log, int indent) {
try {
if (keySize < 512) {
log.add(LogLevel.ERROR, LogType.MSG_CR_ERROR_KEYSIZE_512, indent);
return null;
// Some safety checks
if (add.mAlgorithm == Algorithm.ECDH || add.mAlgorithm == Algorithm.ECDSA) {
if (add.mCurve == null) {
log.add(LogLevel.ERROR, LogType.MSG_CR_ERROR_NO_CURVE, indent);
return null;
}
} else {
if (add.mKeySize == null) {
log.add(LogLevel.ERROR, LogType.MSG_CR_ERROR_NO_KEYSIZE, indent);
return null;
}
if (add.mKeySize < 512) {
log.add(LogLevel.ERROR, LogType.MSG_CR_ERROR_KEYSIZE_512, indent);
return null;
}
}
int algorithm;
KeyPairGenerator keyGen;
switch (algorithmChoice) {
case PublicKeyAlgorithmTags.DSA: {
switch (add.mAlgorithm) {
case DSA: {
if ((add.mFlags & (PGPKeyFlags.CAN_ENCRYPT_COMMS | PGPKeyFlags.CAN_ENCRYPT_STORAGE)) > 0) {
log.add(LogLevel.ERROR, LogType.MSG_CR_ERROR_FLAGS_DSA, indent);
return null;
}
progress(R.string.progress_generating_dsa, 30);
keyGen = KeyPairGenerator.getInstance("DSA", Constants.BOUNCY_CASTLE_PROVIDER_NAME);
keyGen.initialize(keySize, new SecureRandom());
keyGen.initialize(add.mKeySize, new SecureRandom());
algorithm = PGPPublicKey.DSA;
break;
}
case PublicKeyAlgorithmTags.ELGAMAL_ENCRYPT: {
case ELGAMAL: {
if ((add.mFlags & (PGPKeyFlags.CAN_SIGN | PGPKeyFlags.CAN_CERTIFY)) > 0) {
log.add(LogLevel.ERROR, LogType.MSG_CR_ERROR_FLAGS_ELGAMAL, indent);
return null;
}
progress(R.string.progress_generating_elgamal, 30);
keyGen = KeyPairGenerator.getInstance("ElGamal", Constants.BOUNCY_CASTLE_PROVIDER_NAME);
BigInteger p = Primes.getBestPrime(keySize);
BigInteger p = Primes.getBestPrime(add.mKeySize);
BigInteger g = new BigInteger("2");
ElGamalParameterSpec elParams = new ElGamalParameterSpec(p, g);
@@ -189,15 +226,44 @@ public class PgpKeyOperation {
break;
}
case PublicKeyAlgorithmTags.RSA_GENERAL: {
case RSA: {
progress(R.string.progress_generating_rsa, 30);
keyGen = KeyPairGenerator.getInstance("RSA", Constants.BOUNCY_CASTLE_PROVIDER_NAME);
keyGen.initialize(keySize, new SecureRandom());
keyGen.initialize(add.mKeySize, new SecureRandom());
algorithm = PGPPublicKey.RSA_GENERAL;
break;
}
case ECDSA: {
if ((add.mFlags & (PGPKeyFlags.CAN_ENCRYPT_COMMS | PGPKeyFlags.CAN_ENCRYPT_STORAGE)) > 0) {
log.add(LogLevel.ERROR, LogType.MSG_CR_ERROR_FLAGS_ECDSA, indent);
return null;
}
progress(R.string.progress_generating_ecdsa, 30);
ECGenParameterSpec ecParamSpec = getEccParameterSpec(add.mCurve);
keyGen = KeyPairGenerator.getInstance("ECDSA", Constants.BOUNCY_CASTLE_PROVIDER_NAME);
keyGen.initialize(ecParamSpec, new SecureRandom());
algorithm = PGPPublicKey.ECDSA;
break;
}
case ECDH: {
// make sure there are no sign or certify flags set
if ((add.mFlags & (PGPKeyFlags.CAN_SIGN | PGPKeyFlags.CAN_CERTIFY)) > 0) {
log.add(LogLevel.ERROR, LogType.MSG_CR_ERROR_FLAGS_ECDH, indent);
return null;
}
progress(R.string.progress_generating_ecdh, 30);
ECGenParameterSpec ecParamSpec = getEccParameterSpec(add.mCurve);
keyGen = KeyPairGenerator.getInstance("ECDH", Constants.BOUNCY_CASTLE_PROVIDER_NAME);
keyGen.initialize(ecParamSpec, new SecureRandom());
algorithm = PGPPublicKey.ECDH;
break;
}
default: {
log.add(LogLevel.ERROR, LogType.MSG_CR_ERROR_UNKNOWN_ALGO, indent);
return null;
@@ -210,7 +276,8 @@ public class PgpKeyOperation {
} catch(NoSuchProviderException e) {
throw new RuntimeException(e);
} catch(NoSuchAlgorithmException e) {
throw new RuntimeException(e);
log.add(LogLevel.ERROR, LogType.MSG_CR_ERROR_UNKNOWN_ALGO, indent);
return null;
} catch(InvalidAlgorithmParameterException e) {
throw new RuntimeException(e);
} catch(PGPException e) {
@@ -252,13 +319,8 @@ public class PgpKeyOperation {
return new EditKeyResult(EditKeyResult.RESULT_ERROR, log, null);
}
if (add.mAlgorithm == PublicKeyAlgorithmTags.ELGAMAL_ENCRYPT) {
log.add(LogLevel.ERROR, LogType.MSG_CR_ERROR_MASTER_ELGAMAL, indent);
return new EditKeyResult(EditKeyResult.RESULT_ERROR, log, null);
}
subProgressPush(10, 30);
PGPKeyPair keyPair = createKey(add.mAlgorithm, add.mKeysize, log, indent);
PGPKeyPair keyPair = createKey(add, log, indent);
subProgressPop();
// return null if this failed (an error will already have been logged by createKey)
@@ -690,8 +752,8 @@ public class PgpKeyOperation {
progress(R.string.progress_modify_subkeyadd, (i-1) * (100 / saveParcel.mAddSubKeys.size()));
SaveKeyringParcel.SubkeyAdd add = saveParcel.mAddSubKeys.get(i);
log.add(LogLevel.INFO, LogType.MSG_MF_SUBKEY_NEW, indent, Integer.toString(add.mKeysize),
PgpKeyHelper.getAlgorithmInfo(add.mAlgorithm) );
log.add(LogLevel.INFO, LogType.MSG_MF_SUBKEY_NEW, indent,
PgpKeyHelper.getAlgorithmInfo(add.mAlgorithm, add.mKeySize, add.mCurve) );
if (add.mExpiry == null) {
log.add(LogLevel.ERROR, LogType.MSG_MF_ERROR_NULL_EXPIRY, indent +1);
@@ -708,7 +770,7 @@ public class PgpKeyOperation {
(i-1) * (100 / saveParcel.mAddSubKeys.size()),
i * (100 / saveParcel.mAddSubKeys.size())
);
PGPKeyPair keyPair = createKey(add.mAlgorithm, add.mKeysize, log, indent);
PGPKeyPair keyPair = createKey(add, log, indent);
subProgressPop();
if (keyPair == null) {
log.add(LogLevel.ERROR, LogType.MSG_MF_ERROR_PGP, indent +1);

View File

@@ -37,7 +37,6 @@ import org.sufficientlysecure.keychain.pgp.exception.PgpGeneralException;
import org.sufficientlysecure.keychain.service.OperationResultParcel.LogLevel;
import org.sufficientlysecure.keychain.service.OperationResultParcel.LogType;
import org.sufficientlysecure.keychain.service.OperationResultParcel.OperationLog;
import org.sufficientlysecure.keychain.service.OperationResults;
import org.sufficientlysecure.keychain.util.IterableIterator;
import org.sufficientlysecure.keychain.util.Log;
@@ -58,7 +57,8 @@ import java.util.TreeSet;
* This class and its relatives UncachedPublicKey and UncachedSecretKey are
* used to move around pgp key rings in non crypto related (UI, mostly) code.
* It should be used for simple inspection only until it saved in the database,
* all actual crypto operations should work with WrappedKeyRings exclusively.
* all actual crypto operations should work with CanonicalizedKeyRings
* exclusively.
*
* This class is also special in that it can hold either the PGPPublicKeyRing
* or PGPSecretKeyRing derivate of the PGPKeyRing class, since these are
@@ -591,7 +591,7 @@ public class UncachedKeyRing {
}
// if we already have a cert, and this one is not newer: skip it
// if we already have a cert, and this one is older: skip it
if (selfCert != null && cert.getCreationTime().before(selfCert.getCreationTime())) {
log.add(LogLevel.DEBUG, LogType.MSG_KC_SUB_DUP, indent);
redundantCerts += 1;

View File

@@ -18,6 +18,10 @@
package org.sufficientlysecure.keychain.pgp;
import org.spongycastle.asn1.ASN1ObjectIdentifier;
import org.spongycastle.asn1.nist.NISTNamedCurves;
import org.spongycastle.asn1.teletrust.TeleTrusTNamedCurves;
import org.spongycastle.bcpg.ECPublicBCPGKey;
import org.spongycastle.bcpg.sig.KeyFlags;
import org.spongycastle.openpgp.PGPPublicKey;
import org.spongycastle.openpgp.PGPSignature;
@@ -94,10 +98,23 @@ public class UncachedPublicKey {
return mPublicKey.getAlgorithm();
}
public int getBitStrength() {
public Integer getBitStrength() {
if (isEC()) {
return null;
}
return mPublicKey.getBitStrength();
}
public String getCurveOid() {
if ( ! isEC()) {
return null;
}
if ( ! (mPublicKey.getPublicKeyPacket().getKey() instanceof ECPublicBCPGKey)) {
return null;
}
return ((ECPublicBCPGKey) mPublicKey.getPublicKeyPacket().getKey()).getCurveOID().getId();
}
/** Returns the primary user id, as indicated by the public key's self certificates.
*
* This is an expensive operation, since potentially a lot of certificates (and revocations)
@@ -186,6 +203,10 @@ public class UncachedPublicKey {
return getAlgorithm() == PGPPublicKey.DSA;
}
public boolean isEC() {
return getAlgorithm() == PGPPublicKey.ECDH || getAlgorithm() == PGPPublicKey.ECDSA;
}
@SuppressWarnings("unchecked")
// TODO make this safe
public int getKeyUsage() {