package com.wesabe.grendel.openpgp;
import java.security.GeneralSecurityException;
import java.security.KeyPair;
import java.security.KeyPairGenerator;
import java.security.SecureRandom;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
import org.bouncycastle.openpgp.PGPException;
import org.bouncycastle.openpgp.PGPKeyPair;
import org.bouncycastle.openpgp.PGPKeyRingGenerator;
import org.bouncycastle.openpgp.PGPSecretKeyRing;
import org.bouncycastle.openpgp.PGPSignatureSubpacketGenerator;
import org.bouncycastle.openpgp.PGPSignatureSubpacketVector;
import org.joda.time.DateTime;
import com.google.inject.Inject;
import com.google.inject.internal.ImmutableList;
import com.wesabe.grendel.util.IntegerEquivalents;
/**
* A multithreaded generator for {@link KeySet}s.
*
* Generates master keys using {@link AsymmetricAlgorithm#ENCRYPTION_DEFAULT},
* and subkeys using {@link AsymmetricAlgorithm#SIGNING_DEFAULT}.
*
* @author coda
*/
public class KeySetGenerator {
/**
* A {@link Callable} which returns a new key pair.
* @author coda
*
*/
private static class GeneratorTask implements Callable<KeyPair> {
private final AsymmetricAlgorithm algorithm;
private final SecureRandom random;
public GeneratorTask(AsymmetricAlgorithm algorithm, SecureRandom random) {
this.algorithm = algorithm;
this.random = random;
}
@Override
public KeyPair call() throws Exception {
final KeyPairGenerator generator = KeyPairGenerator.getInstance(algorithm.getName(), "BC");
generator.initialize(algorithm.getAlgorithmParameterSpec(), random);
return generator.generateKeyPair();
}
}
private final SecureRandom random;
private final ExecutorService executor;
/**
* Creates a new {@link KeySetGenerator}.
*
* @param random a secure random number generator
*/
@Inject
public KeySetGenerator(SecureRandom random) {
this.random = random;
this.executor = Executors.newCachedThreadPool();
}
/**
* Generates a new {@link KeySet}.
*
* @param userId the user ID, in {@code First Last <email@example.com>} format
* @param passphrase the user's passphrase
* @return a keyset for the user
* @throws CryptographicException if there was an error generating the keyset
*/
public KeySet generate(String userId, char[] passphrase) throws CryptographicException {
try {
final Future<KeyPair> masterKeyPair = generateKeyPair(
AsymmetricAlgorithm.SIGNING_DEFAULT
);
final Future<KeyPair> subKeyPair = generateKeyPair(
AsymmetricAlgorithm.ENCRYPTION_DEFAULT
);
final PGPKeyPair masterPGPKeyPair = new PGPKeyPair(
AsymmetricAlgorithm.SIGNING_DEFAULT.toInteger(),
masterKeyPair.get(),
new DateTime().toDate()
);
final PGPKeyRingGenerator generator = new PGPKeyRingGenerator(
SignatureType.POSITIVE_CERTIFICATION.toInteger(),
masterPGPKeyPair,
userId,
SymmetricAlgorithm.DEFAULT.toInteger(),
passphrase,
true, // use SHA-1 instead of MD5
generateMasterKeySettings(),
null, // don't store any key settings unhashed
random,
"BC"
);
final PGPKeyPair subPGPKeyPair = new PGPKeyPair(
AsymmetricAlgorithm.ENCRYPTION_DEFAULT.toInteger(),
subKeyPair.get(),
new DateTime().toDate()
);
generator.addSubKey(subPGPKeyPair, generateSubKeySettings(), null);
final PGPSecretKeyRing keyRing = generator.generateSecretKeyRing();
return KeySet.load(keyRing);
} catch (GeneralSecurityException e) {
throw new CryptographicException(e);
} catch (PGPException e) {
throw new CryptographicException(e);
} catch (InterruptedException e) {
throw new CryptographicException(e);
} catch (ExecutionException e) {
throw new CryptographicException(e);
}
}
private PGPSignatureSubpacketVector generateSubKeySettings() {
final PGPSignatureSubpacketGenerator settings = new PGPSignatureSubpacketGenerator();
settings.setKeyFlags(false, IntegerEquivalents.toBitmask(KeyFlag.SUB_KEY_DEFAULTS));
return settings.generate();
}
private PGPSignatureSubpacketVector generateMasterKeySettings() {
final PGPSignatureSubpacketGenerator settings = new PGPSignatureSubpacketGenerator();
settings.setKeyFlags(false,
IntegerEquivalents.toBitmask(KeyFlag.MASTER_KEY_DEFAULTS)
);
settings.setPreferredSymmetricAlgorithms(false,
IntegerEquivalents.toIntArray(SymmetricAlgorithm.ACCEPTABLE_ALGORITHMS)
);
settings.setPreferredHashAlgorithms(false,
IntegerEquivalents.toIntArray(HashAlgorithm.ACCEPTABLE_ALGORITHMS)
);
settings.setPreferredCompressionAlgorithms(false,
IntegerEquivalents.toIntArray(
ImmutableList.of(
CompressionAlgorithm.BZIP2,
CompressionAlgorithm.ZLIB,
CompressionAlgorithm.ZIP
)
)
);
return settings.generate();
}
private Future<KeyPair> generateKeyPair(final AsymmetricAlgorithm algorithm) {
return executor.submit(new GeneratorTask(algorithm, random));
}
}