Support of OpenPGP card v3

This commit is contained in:
Arnaud Fontaine
2016-10-25 16:37:43 +02:00
parent 7616c1c8b8
commit 05bfd6bc01
29 changed files with 1403 additions and 167 deletions

View File

@@ -18,14 +18,27 @@
package org.sufficientlysecure.keychain.pgp;
import org.bouncycastle.asn1.ASN1ObjectIdentifier;
import org.bouncycastle.asn1.nist.NISTObjectIdentifiers;
import org.bouncycastle.bcpg.ECDHPublicBCPGKey;
import org.bouncycastle.bcpg.HashAlgorithmTags;
import org.bouncycastle.bcpg.SymmetricKeyAlgorithmTags;
import org.bouncycastle.bcpg.sig.KeyFlags;
import org.bouncycastle.openpgp.PGPException;
import org.bouncycastle.openpgp.PGPPublicKey;
import org.bouncycastle.openpgp.PGPSignature;
import org.bouncycastle.openpgp.operator.KeyFingerPrintCalculator;
import org.bouncycastle.openpgp.operator.RFC6637Utils;
import org.bouncycastle.openpgp.operator.jcajce.JcaPGPKeyConverter;
import org.bouncycastle.openpgp.operator.jcajce.JcePublicKeyKeyEncryptionMethodGenerator;
import org.sufficientlysecure.keychain.Constants;
import org.sufficientlysecure.keychain.pgp.exception.PgpGeneralException;
import org.sufficientlysecure.keychain.util.IterableIterator;
import org.sufficientlysecure.keychain.util.Log;
import java.io.IOException;
import java.security.PublicKey;
import java.security.interfaces.ECPublicKey;
import java.util.Calendar;
import java.util.Date;
import java.util.GregorianCalendar;
@@ -189,4 +202,62 @@ public class CanonicalizedPublicKey extends UncachedPublicKey {
return !isRevoked() && !isExpired();
}
// For use only in card export; returns the public key.
public ECPublicKey getECPublicKey()
throws PgpGeneralException {
JcaPGPKeyConverter keyConverter = new JcaPGPKeyConverter();
PublicKey retVal;
try {
retVal = keyConverter.getPublicKey(mPublicKey);
} catch (PGPException e) {
throw new PgpGeneralException("Error converting public key!", e);
}
return (ECPublicKey) retVal;
}
public ASN1ObjectIdentifier getHashAlgorithm()
throws PGPException {
if (!isEC()) {
throw new PGPException("Key encryption OID is valid only for EC key!");
}
final ECDHPublicBCPGKey eck = (ECDHPublicBCPGKey)mPublicKey.getPublicKeyPacket().getKey();
switch (eck.getHashAlgorithm()) {
case HashAlgorithmTags.SHA256:
return NISTObjectIdentifiers.id_sha256;
case HashAlgorithmTags.SHA384:
return NISTObjectIdentifiers.id_sha384;
case HashAlgorithmTags.SHA512:
return NISTObjectIdentifiers.id_sha512;
default:
throw new PGPException("Invalid hash algorithm for EC key : " + eck.getHashAlgorithm());
}
}
public int getSymmetricKeySize()
throws PGPException {
if (!isEC()) {
throw new PGPException("Key encryption OID is valid only for EC key!");
}
final ECDHPublicBCPGKey eck = (ECDHPublicBCPGKey)mPublicKey.getPublicKeyPacket().getKey();
switch (eck.getSymmetricKeyAlgorithm()) {
case SymmetricKeyAlgorithmTags.AES_128:
return 128;
case SymmetricKeyAlgorithmTags.AES_192:
return 192;
case SymmetricKeyAlgorithmTags.AES_256:
return 256;
default:
throw new PGPException("Invalid symmetric encryption algorithm for EC key : " + eck.getSymmetricKeyAlgorithm());
}
}
public byte[] createUserKeyingMaterial(KeyFingerPrintCalculator fingerPrintCalculator)
throws IOException, PGPException {
return RFC6637Utils.createUserKeyingMaterial(mPublicKey.getPublicKeyPacket(), fingerPrintCalculator);
}
}

View File

@@ -21,6 +21,7 @@ package org.sufficientlysecure.keychain.pgp;
import java.nio.ByteBuffer;
import java.security.PrivateKey;
import java.security.interfaces.ECPrivateKey;
import java.security.interfaces.RSAPrivateCrtKey;
import java.util.Date;
import java.util.HashMap;
@@ -319,6 +320,28 @@ public class CanonicalizedSecretKey extends CanonicalizedPublicKey {
return (RSAPrivateCrtKey)retVal;
}
// For use only in card export; returns the secret key.
public ECPrivateKey getECSecretKey()
throws PgpGeneralException {
if (mPrivateKeyState == PRIVATE_KEY_STATE_LOCKED) {
throw new PgpGeneralException("Cannot get secret key attributes while key is locked.");
}
if (mPrivateKeyState == PRIVATE_KEY_STATE_DIVERT_TO_CARD) {
throw new PgpGeneralException("Cannot get secret key attributes of divert-to-card key.");
}
JcaPGPKeyConverter keyConverter = new JcaPGPKeyConverter();
PrivateKey retVal;
try {
retVal = keyConverter.getPrivateKey(mPrivateKey);
} catch (PGPException e) {
throw new PgpGeneralException("Error converting private key! " + e.getMessage(), e);
}
return (ECPrivateKey) retVal;
}
public byte[] getIv() {
return mSecretKey.getIV();
}

View File

@@ -35,6 +35,10 @@ import java.util.Iterator;
import java.util.Stack;
import java.util.concurrent.atomic.AtomicBoolean;
import org.bouncycastle.asn1.ASN1ObjectIdentifier;
import org.bouncycastle.asn1.nist.NISTNamedCurves;
import org.bouncycastle.bcpg.ECDHPublicBCPGKey;
import org.bouncycastle.bcpg.ECDSAPublicBCPGKey;
import org.bouncycastle.bcpg.PublicKeyAlgorithmTags;
import org.bouncycastle.bcpg.S2K;
import org.bouncycastle.bcpg.sig.Features;
@@ -1645,20 +1649,44 @@ public class PgpKeyOperation {
}
private static boolean checkSecurityTokenCompatibility(PGPSecretKey key, OperationLog log, int indent) {
PGPPublicKey publicKey = key.getPublicKey();
int algorithm = publicKey.getAlgorithm();
if (algorithm != PublicKeyAlgorithmTags.RSA_ENCRYPT &&
algorithm != PublicKeyAlgorithmTags.RSA_SIGN &&
algorithm != PublicKeyAlgorithmTags.RSA_GENERAL) {
log.add(LogType.MSG_MF_ERROR_BAD_SECURITY_TOKEN_ALGO, indent + 1);
return false;
}
// Key size must be 2048
int keySize = publicKey.getBitStrength();
if (keySize != 2048) {
log.add(LogType.MSG_MF_ERROR_BAD_SECURITY_TOKEN_SIZE, indent + 1);
return false;
final PGPPublicKey publicKey = key.getPublicKey();
ASN1ObjectIdentifier curve;
switch (publicKey.getAlgorithm()) {
case PublicKeyAlgorithmTags.RSA_ENCRYPT:
case PublicKeyAlgorithmTags.RSA_SIGN:
case PublicKeyAlgorithmTags.RSA_GENERAL:
// Key size must be at least 2048
if (publicKey.getBitStrength() < 2048) {
log.add(LogType.MSG_MF_ERROR_BAD_SECURITY_TOKEN_SIZE, indent + 1);
return false;
}
break;
case PublicKeyAlgorithmTags.ECDH:
curve = ((ECDHPublicBCPGKey)(publicKey.getPublicKeyPacket().getKey())).getCurveOID();
if (!curve.equals(NISTNamedCurves.getOID("P-256")) &&
!curve.equals(NISTNamedCurves.getOID("P-384")) &&
!curve.equals(NISTNamedCurves.getOID("P-521"))) {
log.add(LogType.MSG_MF_ERROR_BAD_SECURITY_TOKEN_CURVE, indent + 1);
return false;
}
break;
case PublicKeyAlgorithmTags.ECDSA:
curve = ((ECDSAPublicBCPGKey)(publicKey.getPublicKeyPacket().getKey())).getCurveOID();
if (!curve.equals(NISTNamedCurves.getOID("P-256")) &&
!curve.equals(NISTNamedCurves.getOID("P-384")) &&
!curve.equals(NISTNamedCurves.getOID("P-521"))) {
log.add(LogType.MSG_MF_ERROR_BAD_SECURITY_TOKEN_CURVE, indent + 1);
return false;
}
break;
default:
log.add(LogType.MSG_MF_ERROR_BAD_SECURITY_TOKEN_ALGO, indent + 1);
return false;
}
// Secret key parts must be available