diff options
Diffstat (limited to 'OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/CanonicalizedSecretKey.java')
-rw-r--r-- | OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/CanonicalizedSecretKey.java | 239 |
1 files changed, 239 insertions, 0 deletions
diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/CanonicalizedSecretKey.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/CanonicalizedSecretKey.java new file mode 100644 index 000000000..ff82da07a --- /dev/null +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/CanonicalizedSecretKey.java @@ -0,0 +1,239 @@ +package org.sufficientlysecure.keychain.pgp; + +import org.spongycastle.bcpg.HashAlgorithmTags; +import org.spongycastle.bcpg.S2K; +import org.spongycastle.openpgp.PGPException; +import org.spongycastle.openpgp.PGPPrivateKey; +import org.spongycastle.openpgp.PGPPublicKey; +import org.spongycastle.openpgp.PGPPublicKeyRing; +import org.spongycastle.openpgp.PGPSecretKey; +import org.spongycastle.openpgp.PGPSignature; +import org.spongycastle.openpgp.PGPSignatureGenerator; +import org.spongycastle.openpgp.PGPSignatureSubpacketGenerator; +import org.spongycastle.openpgp.PGPSignatureSubpacketVector; +import org.spongycastle.openpgp.PGPUtil; +import org.spongycastle.openpgp.operator.PBESecretKeyDecryptor; +import org.spongycastle.openpgp.operator.PGPContentSignerBuilder; +import org.spongycastle.openpgp.operator.PublicKeyDataDecryptorFactory; +import org.spongycastle.openpgp.operator.jcajce.JcaPGPContentSignerBuilder; +import org.spongycastle.openpgp.operator.jcajce.JcePBESecretKeyDecryptorBuilder; +import org.spongycastle.openpgp.operator.jcajce.JcePublicKeyDataDecryptorFactoryBuilder; +import org.spongycastle.openpgp.operator.jcajce.NfcSyncPGPContentSignerBuilder; +import org.sufficientlysecure.keychain.Constants; +import org.sufficientlysecure.keychain.pgp.exception.PgpGeneralException; +import org.sufficientlysecure.keychain.pgp.exception.PgpGeneralMsgIdException; +import org.sufficientlysecure.keychain.util.IterableIterator; +import org.sufficientlysecure.keychain.util.Log; + +import java.security.NoSuchAlgorithmException; +import java.security.NoSuchProviderException; +import java.security.SignatureException; +import java.util.Date; +import java.util.LinkedList; +import java.util.List; + +/** Wrapper for a PGPSecretKey. + * + * This object can only be obtained from a WrappedSecretKeyRing, and stores a + * back reference to its parent. + * + * This class represents known secret keys which are stored in the database. + * All "crypto operations using a known secret key" should be implemented in + * this class, to ensure on type level that these operations are performed on + * properly imported secret keys only. + * + */ +public class CanonicalizedSecretKey extends CanonicalizedPublicKey { + + private final PGPSecretKey mSecretKey; + private PGPPrivateKey mPrivateKey = null; + + private int mPrivateKeyState = PRIVATE_KEY_STATE_LOCKED; + private static int PRIVATE_KEY_STATE_LOCKED = 0; + private static int PRIVATE_KEY_STATE_UNLOCKED = 1; + private static int PRIVATE_KEY_STATE_DIVERT_TO_CARD = 2; + + CanonicalizedSecretKey(CanonicalizedSecretKeyRing ring, PGPSecretKey key) { + super(ring, key.getPublicKey()); + mSecretKey = key; + } + + public CanonicalizedSecretKeyRing getRing() { + return (CanonicalizedSecretKeyRing) mRing; + } + + /** + * Returns true on right passphrase + */ + public boolean unlock(String passphrase) throws PgpGeneralException { + // handle keys on OpenPGP cards like they were unlocked + if (mSecretKey.getS2K().getType() == S2K.GNU_DUMMY_S2K + && mSecretKey.getS2K().getProtectionMode() == S2K.GNU_PROTECTION_MODE_DIVERT_TO_CARD) { + mPrivateKeyState = PRIVATE_KEY_STATE_DIVERT_TO_CARD; + return true; + } + + // try to extract keys using the passphrase + try { + PBESecretKeyDecryptor keyDecryptor = new JcePBESecretKeyDecryptorBuilder().setProvider( + Constants.BOUNCY_CASTLE_PROVIDER_NAME).build(passphrase.toCharArray()); + mPrivateKey = mSecretKey.extractPrivateKey(keyDecryptor); + mPrivateKeyState = PRIVATE_KEY_STATE_UNLOCKED; + } catch (PGPException e) { + return false; + } + if(mPrivateKey == null) { + throw new PgpGeneralException("error extracting key"); + } + return true; + } + + // TODO: just a hack currently + public LinkedList<Integer> getSupportedHashAlgorithms() { + LinkedList<Integer> supported = new LinkedList<Integer>(); + + if (mPrivateKeyState == PRIVATE_KEY_STATE_DIVERT_TO_CARD) { + // TODO: only works with SHA256 ?! + supported.add(HashAlgorithmTags.SHA256); + } else { + supported.add(HashAlgorithmTags.MD5); + supported.add(HashAlgorithmTags.RIPEMD160); + supported.add(HashAlgorithmTags.SHA1); + supported.add(HashAlgorithmTags.SHA224); + supported.add(HashAlgorithmTags.SHA256); + supported.add(HashAlgorithmTags.SHA384); + supported.add(HashAlgorithmTags.SHA512); // preferred is latest + } + + return supported; + } + + public PGPSignatureGenerator getSignatureGenerator(int hashAlgo, boolean cleartext, + byte[] nfcSignedHash, Date nfcCreationTimestamp) + throws PgpGeneralException { + if (mPrivateKeyState == PRIVATE_KEY_STATE_LOCKED) { + throw new PrivateKeyNotUnlockedException(); + } + + PGPContentSignerBuilder contentSignerBuilder; + if (mPrivateKeyState == PRIVATE_KEY_STATE_DIVERT_TO_CARD) { + // to sign using nfc PgpSignEncrypt is executed two times. + // the first time it stops to return the PendingIntent for nfc connection and signing the hash + // the second time the signed hash is used. + // to get the same hash we cache the timestamp for the second round! + if (nfcCreationTimestamp == null) { + nfcCreationTimestamp = new Date(); + } + + // use synchronous "NFC based" SignerBuilder + contentSignerBuilder = new NfcSyncPGPContentSignerBuilder( + mSecretKey.getPublicKey().getAlgorithm(), hashAlgo, + mSecretKey.getKeyID(), nfcSignedHash, nfcCreationTimestamp) + .setProvider(Constants.BOUNCY_CASTLE_PROVIDER_NAME); + + Log.d(Constants.TAG, "mSecretKey.getKeyID() "+ PgpKeyHelper.convertKeyIdToHex(mSecretKey.getKeyID())); + } else { + // content signer based on signing key algorithm and chosen hash algorithm + contentSignerBuilder = new JcaPGPContentSignerBuilder( + mSecretKey.getPublicKey().getAlgorithm(), hashAlgo) + .setProvider(Constants.BOUNCY_CASTLE_PROVIDER_NAME); + } + + int signatureType; + if (cleartext) { + // for sign-only ascii text + signatureType = PGPSignature.CANONICAL_TEXT_DOCUMENT; + } else { + signatureType = PGPSignature.BINARY_DOCUMENT; + } + + try { + PGPSignatureGenerator signatureGenerator = new PGPSignatureGenerator(contentSignerBuilder); + signatureGenerator.init(signatureType, mPrivateKey); + + PGPSignatureSubpacketGenerator spGen = new PGPSignatureSubpacketGenerator(); + spGen.setSignerUserID(false, mRing.getPrimaryUserIdWithFallback()); + if (nfcCreationTimestamp != null) { + spGen.setSignatureCreationTime(false, nfcCreationTimestamp); + Log.d(Constants.TAG, "For NFC: set sig creation time to " + nfcCreationTimestamp); + } + signatureGenerator.setHashedSubpackets(spGen.generate()); + return signatureGenerator; + } catch(PGPException e) { + // TODO: simply throw PGPException! + throw new PgpGeneralException("Error initializing signature!", e); + } + } + + public PublicKeyDataDecryptorFactory getDecryptorFactory() { + // TODO: divert to card missing + if (mPrivateKeyState != PRIVATE_KEY_STATE_UNLOCKED) { + throw new PrivateKeyNotUnlockedException(); + } + return new JcePublicKeyDataDecryptorFactoryBuilder() + .setProvider(Constants.BOUNCY_CASTLE_PROVIDER_NAME).build(mPrivateKey); + } + + /** + * Certify the given pubkeyid with the given masterkeyid. + * + * @param publicKeyRing Keyring to add certification to. + * @param userIds User IDs to certify, must not be null or empty + * @return A keyring with added certifications + */ + public UncachedKeyRing certifyUserIds(CanonicalizedPublicKeyRing publicKeyRing, List<String> userIds) + throws PgpGeneralMsgIdException, NoSuchAlgorithmException, NoSuchProviderException, + PGPException, SignatureException { + + // TODO: divert to card missing + if (mPrivateKeyState != PRIVATE_KEY_STATE_UNLOCKED) { + throw new PrivateKeyNotUnlockedException(); + } + + // create a signatureGenerator from the supplied masterKeyId and passphrase + PGPSignatureGenerator signatureGenerator; + { + // TODO: SHA256 fixed? + JcaPGPContentSignerBuilder contentSignerBuilder = new JcaPGPContentSignerBuilder( + mSecretKey.getPublicKey().getAlgorithm(), PGPUtil.SHA256) + .setProvider(Constants.BOUNCY_CASTLE_PROVIDER_NAME); + + signatureGenerator = new PGPSignatureGenerator(contentSignerBuilder); + signatureGenerator.init(PGPSignature.DEFAULT_CERTIFICATION, mPrivateKey); + } + + { // supply signatureGenerator with a SubpacketVector + PGPSignatureSubpacketGenerator spGen = new PGPSignatureSubpacketGenerator(); + PGPSignatureSubpacketVector packetVector = spGen.generate(); + signatureGenerator.setHashedSubpackets(packetVector); + } + + // get the master subkey (which we certify for) + PGPPublicKey publicKey = publicKeyRing.getPublicKey().getPublicKey(); + + // fetch public key ring, add the certification and return it + for (String userId : new IterableIterator<String>(userIds.iterator())) { + PGPSignature sig = signatureGenerator.generateCertification(userId, publicKey); + publicKey = PGPPublicKey.addCertification(publicKey, userId, sig); + } + + PGPPublicKeyRing ring = PGPPublicKeyRing.insertPublicKey(publicKeyRing.getRing(), publicKey); + + return new UncachedKeyRing(ring); + } + + static class PrivateKeyNotUnlockedException extends RuntimeException { + // this exception is a programming error which happens when an operation which requires + // the private key is called without a previous call to unlock() + } + + public UncachedSecretKey getUncached() { + return new UncachedSecretKey(mSecretKey); + } + + // HACK + public PGPSecretKey getSecretKey() { + return mSecretKey; + } + +} |