diff options
Diffstat (limited to 'pg/src/main/java/org/spongycastle/openpgp/operator')
60 files changed, 5470 insertions, 0 deletions
diff --git a/pg/src/main/java/org/spongycastle/openpgp/operator/KeyFingerPrintCalculator.java b/pg/src/main/java/org/spongycastle/openpgp/operator/KeyFingerPrintCalculator.java new file mode 100644 index 00000000..a5a8a269 --- /dev/null +++ b/pg/src/main/java/org/spongycastle/openpgp/operator/KeyFingerPrintCalculator.java @@ -0,0 +1,10 @@ +package org.spongycastle.openpgp.operator; + +import org.spongycastle.bcpg.PublicKeyPacket; +import org.spongycastle.openpgp.PGPException; + +public interface KeyFingerPrintCalculator +{ + byte[] calculateFingerprint(PublicKeyPacket publicPk) + throws PGPException; +} diff --git a/pg/src/main/java/org/spongycastle/openpgp/operator/PBEDataDecryptorFactory.java b/pg/src/main/java/org/spongycastle/openpgp/operator/PBEDataDecryptorFactory.java new file mode 100644 index 00000000..5660468e --- /dev/null +++ b/pg/src/main/java/org/spongycastle/openpgp/operator/PBEDataDecryptorFactory.java @@ -0,0 +1,57 @@ +package org.spongycastle.openpgp.operator; + +import org.spongycastle.bcpg.S2K; +import org.spongycastle.bcpg.SymmetricKeyAlgorithmTags; +import org.spongycastle.openpgp.PGPException; + +/** + * A factory for performing PBE decryption operations. + */ +public abstract class PBEDataDecryptorFactory + implements PGPDataDecryptorFactory +{ + private char[] passPhrase; + private PGPDigestCalculatorProvider calculatorProvider; + + /** + * Construct a PBE data decryptor factory. + * + * @param passPhrase the pass phrase to generate decryption keys with. + * @param calculatorProvider the digest to use in key generation. + */ + protected PBEDataDecryptorFactory(char[] passPhrase, PGPDigestCalculatorProvider calculatorProvider) + { + this.passPhrase = passPhrase; + this.calculatorProvider = calculatorProvider; + } + + /** + * Generates an encryption key using the pass phrase and digest calculator configured for this + * factory. + * + * @param keyAlgorithm the {@link SymmetricKeyAlgorithmTags encryption algorithm} to generate a + * key for. + * @param s2k the string-to-key specification to use to generate the key. + * @return the key bytes for the encryption algorithm, generated using the pass phrase of this + * factory. + * @throws PGPException if an error occurs generating the key. + */ + public byte[] makeKeyFromPassPhrase(int keyAlgorithm, S2K s2k) + throws PGPException + { + return PGPUtil.makeKeyFromPassPhrase(calculatorProvider, keyAlgorithm, s2k, passPhrase); + } + + /** + * Decrypts session data from an encrypted data packet. + * + * @param keyAlgorithm the {@link SymmetricKeyAlgorithmTags encryption algorithm} used to + * encrypt the session data. + * @param key the key bytes for the encryption algorithm. + * @param seckKeyData the encrypted session data to decrypt. + * @return the decrypted session data. + * @throws PGPException if an error occurs decrypting the session data. + */ + public abstract byte[] recoverSessionData(int keyAlgorithm, byte[] key, byte[] seckKeyData) + throws PGPException; +} diff --git a/pg/src/main/java/org/spongycastle/openpgp/operator/PBEKeyEncryptionMethodGenerator.java b/pg/src/main/java/org/spongycastle/openpgp/operator/PBEKeyEncryptionMethodGenerator.java new file mode 100644 index 00000000..55a3e64b --- /dev/null +++ b/pg/src/main/java/org/spongycastle/openpgp/operator/PBEKeyEncryptionMethodGenerator.java @@ -0,0 +1,134 @@ +package org.spongycastle.openpgp.operator; + +import java.security.SecureRandom; + +import org.spongycastle.bcpg.ContainedPacket; +import org.spongycastle.bcpg.S2K; +import org.spongycastle.bcpg.SymmetricKeyAlgorithmTags; +import org.spongycastle.bcpg.SymmetricKeyEncSessionPacket; +import org.spongycastle.openpgp.PGPException; + +/** + * PGP style PBE encryption method. + * <p/> + * A pass phrase is used to generate an encryption key using the PGP {@link S2K string-to-key} + * method. This class always uses the {@link S2K#SALTED_AND_ITERATED salted and iterated form of the + * S2K algorithm}. + * <p/> + * Note that the iteration count provided to this method is a single byte as described by the + * {@link S2K} algorithm, and the actual iteration count ranges exponentially from + * <code>0x01<code> == 1088 to <code>0xFF</code> == 65,011,712. + */ +public abstract class PBEKeyEncryptionMethodGenerator + extends PGPKeyEncryptionMethodGenerator +{ + private char[] passPhrase; + private PGPDigestCalculator s2kDigestCalculator; + private S2K s2k; + private SecureRandom random; + private int s2kCount; + + /** + * Construct a PBE key generator using the default iteration count (<code>0x60</code> == 65536 + * iterations). + * + * @param passPhrase the pass phrase to encrypt with. + * @param s2kDigestCalculator a digest calculator to use in the string-to-key function. + */ + protected PBEKeyEncryptionMethodGenerator( + char[] passPhrase, + PGPDigestCalculator s2kDigestCalculator) + { + this(passPhrase, s2kDigestCalculator, 0x60); + } + + /** + * Construct a PBE key generator using a specific iteration level. + * + * @param passPhrase the pass phrase to encrypt with. + * @param s2kDigestCalculator a digest calculator to use in the string-to-key function. + * @param s2kCount a single byte {@link S2K} iteration count specifier, which is translated to + * an actual iteration count by the S2K class. + */ + protected PBEKeyEncryptionMethodGenerator( + char[] passPhrase, + PGPDigestCalculator s2kDigestCalculator, + int s2kCount) + { + this.passPhrase = passPhrase; + this.s2kDigestCalculator = s2kDigestCalculator; + + if (s2kCount < 0 || s2kCount > 0xff) + { + throw new IllegalArgumentException("s2kCount value outside of range 0 to 255."); + } + + this.s2kCount = s2kCount; + } + + /** + * Sets a user defined source of randomness. + * <p/> + * If no SecureRandom is configured, a default SecureRandom will be used. + * + * @return the current generator. + */ + public PBEKeyEncryptionMethodGenerator setSecureRandom(SecureRandom random) + { + this.random = random; + + return this; + } + + /** + * Generate a key for a symmetric encryption algorithm using the PBE configuration in this + * method. + * + * @param encAlgorithm the {@link SymmetricKeyAlgorithmTags encryption algorithm} to generate + * the key for. + * @return the bytes of the generated key. + * @throws PGPException if an error occurs performing the string-to-key generation. + */ + public byte[] getKey(int encAlgorithm) + throws PGPException + { + if (s2k == null) + { + byte[] iv = new byte[8]; + + if (random == null) + { + random = new SecureRandom(); + } + + random.nextBytes(iv); + + s2k = new S2K(s2kDigestCalculator.getAlgorithm(), iv, s2kCount); + } + + return PGPUtil.makeKeyFromPassPhrase(s2kDigestCalculator, encAlgorithm, s2k, passPhrase); + } + + public ContainedPacket generate(int encAlgorithm, byte[] sessionInfo) + throws PGPException + { + byte[] key = getKey(encAlgorithm); + + if (sessionInfo == null) + { + return new SymmetricKeyEncSessionPacket(encAlgorithm, s2k, null); + } + + // + // the passed in session info has the an RSA/ElGamal checksum added to it, for PBE this is not included. + // + byte[] nSessionInfo = new byte[sessionInfo.length - 2]; + + System.arraycopy(sessionInfo, 0, nSessionInfo, 0, nSessionInfo.length); + + return new SymmetricKeyEncSessionPacket(encAlgorithm, s2k, encryptSessionInfo(encAlgorithm, key, nSessionInfo)); + } + + abstract protected byte[] encryptSessionInfo(int encAlgorithm, byte[] key, byte[] sessionInfo) + throws PGPException; +} diff --git a/pg/src/main/java/org/spongycastle/openpgp/operator/PBEProtectionRemoverFactory.java b/pg/src/main/java/org/spongycastle/openpgp/operator/PBEProtectionRemoverFactory.java new file mode 100644 index 00000000..99166f6f --- /dev/null +++ b/pg/src/main/java/org/spongycastle/openpgp/operator/PBEProtectionRemoverFactory.java @@ -0,0 +1,9 @@ +package org.spongycastle.openpgp.operator; + +import org.spongycastle.openpgp.PGPException; + +public interface PBEProtectionRemoverFactory +{ + PBESecretKeyDecryptor createDecryptor(String protection) + throws PGPException; +} diff --git a/pg/src/main/java/org/spongycastle/openpgp/operator/PBESecretKeyDecryptor.java b/pg/src/main/java/org/spongycastle/openpgp/operator/PBESecretKeyDecryptor.java new file mode 100644 index 00000000..2f75702f --- /dev/null +++ b/pg/src/main/java/org/spongycastle/openpgp/operator/PBESecretKeyDecryptor.java @@ -0,0 +1,31 @@ +package org.spongycastle.openpgp.operator; + +import org.spongycastle.bcpg.S2K; +import org.spongycastle.openpgp.PGPException; + +public abstract class PBESecretKeyDecryptor +{ + private char[] passPhrase; + private PGPDigestCalculatorProvider calculatorProvider; + + protected PBESecretKeyDecryptor(char[] passPhrase, PGPDigestCalculatorProvider calculatorProvider) + { + this.passPhrase = passPhrase; + this.calculatorProvider = calculatorProvider; + } + + public PGPDigestCalculator getChecksumCalculator(int hashAlgorithm) + throws PGPException + { + return calculatorProvider.get(hashAlgorithm); + } + + public byte[] makeKeyFromPassPhrase(int keyAlgorithm, S2K s2k) + throws PGPException + { + return PGPUtil.makeKeyFromPassPhrase(calculatorProvider, keyAlgorithm, s2k, passPhrase); + } + + public abstract byte[] recoverKeyData(int encAlgorithm, byte[] key, byte[] iv, byte[] keyData, int keyOff, int keyLen) + throws PGPException; +} diff --git a/pg/src/main/java/org/spongycastle/openpgp/operator/PBESecretKeyEncryptor.java b/pg/src/main/java/org/spongycastle/openpgp/operator/PBESecretKeyEncryptor.java new file mode 100644 index 00000000..2bd1bf8f --- /dev/null +++ b/pg/src/main/java/org/spongycastle/openpgp/operator/PBESecretKeyEncryptor.java @@ -0,0 +1,104 @@ +package org.spongycastle.openpgp.operator; + +import java.security.SecureRandom; + +import org.spongycastle.bcpg.S2K; +import org.spongycastle.openpgp.PGPException; + +public abstract class PBESecretKeyEncryptor +{ + protected int encAlgorithm; + protected char[] passPhrase; + protected PGPDigestCalculator s2kDigestCalculator; + protected int s2kCount; + protected S2K s2k; + + protected SecureRandom random; + + protected PBESecretKeyEncryptor(int encAlgorithm, PGPDigestCalculator s2kDigestCalculator, SecureRandom random, char[] passPhrase) + { + this(encAlgorithm, s2kDigestCalculator, 0x60, random, passPhrase); + } + + protected PBESecretKeyEncryptor(int encAlgorithm, PGPDigestCalculator s2kDigestCalculator, int s2kCount, SecureRandom random, char[] passPhrase) + { + this.encAlgorithm = encAlgorithm; + this.passPhrase = passPhrase; + this.random = random; + this.s2kDigestCalculator = s2kDigestCalculator; + + if (s2kCount < 0 || s2kCount > 0xff) + { + throw new IllegalArgumentException("s2kCount value outside of range 0 to 255."); + } + + this.s2kCount = s2kCount; + } + + public int getAlgorithm() + { + return encAlgorithm; + } + + public int getHashAlgorithm() + { + if (s2kDigestCalculator != null) + { + return s2kDigestCalculator.getAlgorithm(); + } + + return -1; + } + + public byte[] getKey() + throws PGPException + { + return PGPUtil.makeKeyFromPassPhrase(s2kDigestCalculator, encAlgorithm, s2k, passPhrase); + } + + public S2K getS2K() + { + return s2k; + } + + /** + * Key encryption method invoked for V4 keys and greater. + * + * @param keyData raw key data + * @param keyOff offset into rawe key data + * @param keyLen length of key data to use. + * @return an encryption of the passed in keyData. + * @throws PGPException on error in the underlying encryption process. + */ + public byte[] encryptKeyData(byte[] keyData, int keyOff, int keyLen) + throws PGPException + { + if (s2k == null) + { + byte[] iv = new byte[8]; + + random.nextBytes(iv); + + s2k = new S2K(s2kDigestCalculator.getAlgorithm(), iv, s2kCount); + } + + return encryptKeyData(getKey(), keyData, keyOff, keyLen); + } + + public abstract byte[] encryptKeyData(byte[] key, byte[] keyData, int keyOff, int keyLen) + throws PGPException; + + /** + * Encrypt the passed in keyData using the key and the iv provided. + * <p> + * This method is only used for processing version 3 keys. + * </p> + */ + public byte[] encryptKeyData(byte[] key, byte[] iv, byte[] keyData, int keyOff, int keyLen) + throws PGPException + { + throw new PGPException("encryption of version 3 keys not supported."); + } + + public abstract byte[] getCipherIV(); +} diff --git a/pg/src/main/java/org/spongycastle/openpgp/operator/PGPContentSigner.java b/pg/src/main/java/org/spongycastle/openpgp/operator/PGPContentSigner.java new file mode 100644 index 00000000..7a3891e6 --- /dev/null +++ b/pg/src/main/java/org/spongycastle/openpgp/operator/PGPContentSigner.java @@ -0,0 +1,20 @@ +package org.spongycastle.openpgp.operator; + +import java.io.OutputStream; + +public interface PGPContentSigner +{ + public OutputStream getOutputStream(); + + byte[] getSignature(); + + byte[] getDigest(); + + int getType(); + + int getHashAlgorithm(); + + int getKeyAlgorithm(); + + long getKeyID(); +} diff --git a/pg/src/main/java/org/spongycastle/openpgp/operator/PGPContentSignerBuilder.java b/pg/src/main/java/org/spongycastle/openpgp/operator/PGPContentSignerBuilder.java new file mode 100644 index 00000000..44f8c8dd --- /dev/null +++ b/pg/src/main/java/org/spongycastle/openpgp/operator/PGPContentSignerBuilder.java @@ -0,0 +1,10 @@ +package org.spongycastle.openpgp.operator; + +import org.spongycastle.openpgp.PGPException; +import org.spongycastle.openpgp.PGPPrivateKey; + +public interface PGPContentSignerBuilder +{ + public PGPContentSigner build(final int signatureType, final PGPPrivateKey privateKey) + throws PGPException; +} diff --git a/pg/src/main/java/org/spongycastle/openpgp/operator/PGPContentVerifier.java b/pg/src/main/java/org/spongycastle/openpgp/operator/PGPContentVerifier.java new file mode 100644 index 00000000..7ed1aea6 --- /dev/null +++ b/pg/src/main/java/org/spongycastle/openpgp/operator/PGPContentVerifier.java @@ -0,0 +1,20 @@ +package org.spongycastle.openpgp.operator; + +import java.io.OutputStream; + +public interface PGPContentVerifier +{ + public OutputStream getOutputStream(); + + int getHashAlgorithm(); + + int getKeyAlgorithm(); + + long getKeyID(); + + /** + * @param expected expected value of the signature on the data. + * @return true if the signature verifies, false otherwise + */ + boolean verify(byte[] expected); +} diff --git a/pg/src/main/java/org/spongycastle/openpgp/operator/PGPContentVerifierBuilder.java b/pg/src/main/java/org/spongycastle/openpgp/operator/PGPContentVerifierBuilder.java new file mode 100644 index 00000000..9e8e4a94 --- /dev/null +++ b/pg/src/main/java/org/spongycastle/openpgp/operator/PGPContentVerifierBuilder.java @@ -0,0 +1,10 @@ +package org.spongycastle.openpgp.operator; + +import org.spongycastle.openpgp.PGPException; +import org.spongycastle.openpgp.PGPPublicKey; + +public interface PGPContentVerifierBuilder +{ + public PGPContentVerifier build(final PGPPublicKey publicKey) + throws PGPException; +} diff --git a/pg/src/main/java/org/spongycastle/openpgp/operator/PGPContentVerifierBuilderProvider.java b/pg/src/main/java/org/spongycastle/openpgp/operator/PGPContentVerifierBuilderProvider.java new file mode 100644 index 00000000..44c138bf --- /dev/null +++ b/pg/src/main/java/org/spongycastle/openpgp/operator/PGPContentVerifierBuilderProvider.java @@ -0,0 +1,9 @@ +package org.spongycastle.openpgp.operator; + +import org.spongycastle.openpgp.PGPException; + +public interface PGPContentVerifierBuilderProvider +{ + public PGPContentVerifierBuilder get(int keyAlgorithm, int hashAlgorithm) + throws PGPException; +} diff --git a/pg/src/main/java/org/spongycastle/openpgp/operator/PGPDataDecryptor.java b/pg/src/main/java/org/spongycastle/openpgp/operator/PGPDataDecryptor.java new file mode 100644 index 00000000..ceeca672 --- /dev/null +++ b/pg/src/main/java/org/spongycastle/openpgp/operator/PGPDataDecryptor.java @@ -0,0 +1,30 @@ +package org.spongycastle.openpgp.operator; + +import java.io.InputStream; + +/** + * A decryptor that wraps a stream of PGP encrypted data to decrypt, and optionally integrity check, + * the data. + */ +public interface PGPDataDecryptor +{ + /** + * Wraps an encrypted data stream with a stream that will return the decrypted data. + * + * @param in the encrypted data. + * @return a decrypting stream. + */ + InputStream getInputStream(InputStream in); + + /** + * Obtains the block size of the encryption algorithm used in this decryptor. + * + * @return the block size of the cipher in bytes. + */ + int getBlockSize(); + + /** + * Obtains the digest calculator used to verify the integrity check. + */ + PGPDigestCalculator getIntegrityCalculator(); +} diff --git a/pg/src/main/java/org/spongycastle/openpgp/operator/PGPDataDecryptorFactory.java b/pg/src/main/java/org/spongycastle/openpgp/operator/PGPDataDecryptorFactory.java new file mode 100644 index 00000000..7223d039 --- /dev/null +++ b/pg/src/main/java/org/spongycastle/openpgp/operator/PGPDataDecryptorFactory.java @@ -0,0 +1,25 @@ +package org.spongycastle.openpgp.operator; + +import org.spongycastle.bcpg.SymmetricKeyAlgorithmTags; +import org.spongycastle.openpgp.PGPException; + +/** + * Base interface of factories for {@link PGPDataDecryptor}. + */ +public interface PGPDataDecryptorFactory +{ + /** + * Constructs a data decryptor. + * + * @param withIntegrityPacket <code>true</code> if the packet to be decrypted has integrity + * checking enabled. + * @param encAlgorithm the identifier of the {@link SymmetricKeyAlgorithmTags encryption + * algorithm} to decrypt with. + * @param key the bytes of the key for the cipher. + * @return a data decryptor that can decrypt (and verify) streams of encrypted data. + * @throws PGPException if an error occurs initialising the decryption and integrity checking + * functions. + */ + public PGPDataDecryptor createDataDecryptor(boolean withIntegrityPacket, int encAlgorithm, byte[] key) + throws PGPException; +} diff --git a/pg/src/main/java/org/spongycastle/openpgp/operator/PGPDataDecryptorProvider.java b/pg/src/main/java/org/spongycastle/openpgp/operator/PGPDataDecryptorProvider.java new file mode 100644 index 00000000..9e87a565 --- /dev/null +++ b/pg/src/main/java/org/spongycastle/openpgp/operator/PGPDataDecryptorProvider.java @@ -0,0 +1,5 @@ +package org.spongycastle.openpgp.operator; + +public interface PGPDataDecryptorProvider +{ +} diff --git a/pg/src/main/java/org/spongycastle/openpgp/operator/PGPDataEncryptor.java b/pg/src/main/java/org/spongycastle/openpgp/operator/PGPDataEncryptor.java new file mode 100644 index 00000000..9811a6de --- /dev/null +++ b/pg/src/main/java/org/spongycastle/openpgp/operator/PGPDataEncryptor.java @@ -0,0 +1,39 @@ +package org.spongycastle.openpgp.operator; + +import java.io.OutputStream; + +/** + * A data encryptor, combining a cipher instance and an optional integrity check calculator. + * <p/> + * {@link PGPDataEncryptor} instances are generally not constructed directly, but obtained from a + * {@link PGPDataEncryptorBuilder}. + */ +public interface PGPDataEncryptor +{ + /** + * Constructs an encrypting output stream that encrypts data using the underlying cipher of this + * encryptor. + * <p/> + * The cipher instance in this encryptor is used for all output streams obtained from this + * method, so it should only be invoked once. + * + * @param out the stream to wrap and write encrypted data to. + * @return a cipher output stream appropriate to the type of this data encryptor. + */ + OutputStream getOutputStream(OutputStream out); + + /** + * Obtains the integrity check calculator configured for this encryptor instance. + * + * @return the integrity check calculator, or <code>null</code> if no integrity checking was + * configured. + */ + PGPDigestCalculator getIntegrityCalculator(); + + /** + * Gets the block size of the underlying cipher used by this encryptor. + * + * @return the block size in bytes. + */ + int getBlockSize(); +} diff --git a/pg/src/main/java/org/spongycastle/openpgp/operator/PGPDataEncryptorBuilder.java b/pg/src/main/java/org/spongycastle/openpgp/operator/PGPDataEncryptorBuilder.java new file mode 100644 index 00000000..a889b3be --- /dev/null +++ b/pg/src/main/java/org/spongycastle/openpgp/operator/PGPDataEncryptorBuilder.java @@ -0,0 +1,36 @@ +package org.spongycastle.openpgp.operator; + +import java.security.SecureRandom; + +import org.spongycastle.bcpg.SymmetricKeyAlgorithmTags; +import org.spongycastle.openpgp.PGPException; + +/** + * A builder for {@link PGPDataEncryptor} instances, which can be used to encrypt data objects. + */ +public interface PGPDataEncryptorBuilder +{ + /** + * The encryption algorithm used by data encryptors created by this builder. + * + * @return one of the {@link SymmetricKeyAlgorithmTags symmetric encryption algorithms}. + */ + int getAlgorithm(); + + /** + * Builds a data encryptor using the algorithm configured for this builder. + * + * @param keyBytes the bytes of the key to use for the cipher. + * @return a data encryptor with an initialised cipher. + * @throws PGPException if an error occurs initialising the configured encryption. + */ + PGPDataEncryptor build(byte[] keyBytes) + throws PGPException; + + /** + * Gets the SecureRandom instance used by this builder. <br/> + * If a SecureRandom has not been explicitly configured, a default {@link SecureRandom} is + * constructed and retained by the this builder. + */ + SecureRandom getSecureRandom(); +} diff --git a/pg/src/main/java/org/spongycastle/openpgp/operator/PGPDigestCalculator.java b/pg/src/main/java/org/spongycastle/openpgp/operator/PGPDigestCalculator.java new file mode 100644 index 00000000..a4b926b0 --- /dev/null +++ b/pg/src/main/java/org/spongycastle/openpgp/operator/PGPDigestCalculator.java @@ -0,0 +1,40 @@ +package org.spongycastle.openpgp.operator; + +import java.io.OutputStream; + +import org.spongycastle.bcpg.HashAlgorithmTags; + +/** + * A digest calculator, which consumes a stream of data and computes a digest value over it. + */ +public interface PGPDigestCalculator +{ + /** + * Return the {@link HashAlgorithmTags algorithm number} representing the digest implemented by + * this calculator. + * + * @return the hash algorithm number + */ + int getAlgorithm(); + + /** + * Returns a stream that will accept data for the purpose of calculating a digest. Use + * org.spongycastle.util.io.TeeOutputStream if you want to accumulate the data on the fly as + * well. + * + * @return an OutputStream that data to be digested can be written to. + */ + OutputStream getOutputStream(); + + /** + * Return the digest calculated on what has been written to the calculator's output stream. + * + * @return a digest. + */ + byte[] getDigest(); + + /** + * Reset the underlying digest calculator + */ + void reset(); +} diff --git a/pg/src/main/java/org/spongycastle/openpgp/operator/PGPDigestCalculatorProvider.java b/pg/src/main/java/org/spongycastle/openpgp/operator/PGPDigestCalculatorProvider.java new file mode 100644 index 00000000..8ad35b13 --- /dev/null +++ b/pg/src/main/java/org/spongycastle/openpgp/operator/PGPDigestCalculatorProvider.java @@ -0,0 +1,21 @@ +package org.spongycastle.openpgp.operator; + +import org.spongycastle.bcpg.HashAlgorithmTags; +import org.spongycastle.openpgp.PGPException; + +/** + * A factory for digest algorithms. + */ +public interface PGPDigestCalculatorProvider +{ + /** + * Construct a new instance of a cryptographic digest. + * + * @param algorithm the identifier of the {@link HashAlgorithmTags digest algorithm} to + * instantiate. + * @return a digest calculator for the specified algorithm. + * @throws PGPException if an error occurs constructing the specified digest. + */ + PGPDigestCalculator get(int algorithm) + throws PGPException; +} diff --git a/pg/src/main/java/org/spongycastle/openpgp/operator/PGPKeyEncryptionMethodGenerator.java b/pg/src/main/java/org/spongycastle/openpgp/operator/PGPKeyEncryptionMethodGenerator.java new file mode 100644 index 00000000..d0a3c5b6 --- /dev/null +++ b/pg/src/main/java/org/spongycastle/openpgp/operator/PGPKeyEncryptionMethodGenerator.java @@ -0,0 +1,23 @@ +package org.spongycastle.openpgp.operator; + +import org.spongycastle.bcpg.ContainedPacket; +import org.spongycastle.bcpg.SymmetricKeyAlgorithmTags; +import org.spongycastle.openpgp.PGPEncryptedDataGenerator; +import org.spongycastle.openpgp.PGPException; + +/** + * An encryption method that can be applied to encrypt data in a {@link PGPEncryptedDataGenerator}. + */ +public abstract class PGPKeyEncryptionMethodGenerator +{ + /** + * Generates a packet encoding the details of this encryption method. + * + * @param encAlgorithm the {@link SymmetricKeyAlgorithmTags encryption algorithm} being used + * @param sessionInfo session data generated by the encrypted data generator. + * @return a packet encoding the provided information and the configuration of this instance. + * @throws PGPException if an error occurs constructing the packet. + */ + public abstract ContainedPacket generate(int encAlgorithm, byte[] sessionInfo) + throws PGPException; +} diff --git a/pg/src/main/java/org/spongycastle/openpgp/operator/PGPPad.java b/pg/src/main/java/org/spongycastle/openpgp/operator/PGPPad.java new file mode 100644 index 00000000..d3ef360c --- /dev/null +++ b/pg/src/main/java/org/spongycastle/openpgp/operator/PGPPad.java @@ -0,0 +1,50 @@ +package org.spongycastle.openpgp.operator; + +import org.spongycastle.openpgp.PGPException; + +/** + * Utility class that provides padding addition and removal for PGP session keys. + */ +public class PGPPad +{ + private PGPPad() + { + + } + + public static byte[] padSessionData(byte[] sessionInfo) + { + byte[] result = new byte[40]; + + System.arraycopy(sessionInfo, 0, result, 0, sessionInfo.length); + + byte padValue = (byte)(result.length - sessionInfo.length); + + for (int i = sessionInfo.length; i != result.length; i++) + { + result[i] = padValue; + } + + return result; + } + + public static byte[] unpadSessionData(byte[] encoded) + throws PGPException + { + byte padValue = encoded[encoded.length - 1]; + + for (int i = encoded.length - padValue; i != encoded.length; i++) + { + if (encoded[i] != padValue) + { + throw new PGPException("bad padding found in session data"); + } + } + + byte[] taggedKey = new byte[encoded.length - padValue]; + + System.arraycopy(encoded, 0, taggedKey, 0, taggedKey.length); + + return taggedKey; + } +} diff --git a/pg/src/main/java/org/spongycastle/openpgp/operator/PGPUtil.java b/pg/src/main/java/org/spongycastle/openpgp/operator/PGPUtil.java new file mode 100644 index 00000000..c318c673 --- /dev/null +++ b/pg/src/main/java/org/spongycastle/openpgp/operator/PGPUtil.java @@ -0,0 +1,229 @@ +package org.spongycastle.openpgp.operator; + +import java.io.IOException; +import java.io.OutputStream; + +import org.spongycastle.bcpg.HashAlgorithmTags; +import org.spongycastle.bcpg.S2K; +import org.spongycastle.bcpg.SymmetricKeyAlgorithmTags; +import org.spongycastle.openpgp.PGPException; +import org.spongycastle.util.Strings; + +/** + * Basic utility class + */ +class PGPUtil + implements HashAlgorithmTags +{ + static byte[] makeKeyFromPassPhrase( + PGPDigestCalculator digestCalculator, + int algorithm, + S2K s2k, + char[] passPhrase) + throws PGPException + { + // TODO: Never used + String algName = null; + int keySize = 0; + + switch (algorithm) + { + case SymmetricKeyAlgorithmTags.TRIPLE_DES: + keySize = 192; + algName = "DES_EDE"; + break; + case SymmetricKeyAlgorithmTags.IDEA: + keySize = 128; + algName = "IDEA"; + break; + case SymmetricKeyAlgorithmTags.CAST5: + keySize = 128; + algName = "CAST5"; + break; + case SymmetricKeyAlgorithmTags.BLOWFISH: + keySize = 128; + algName = "Blowfish"; + break; + case SymmetricKeyAlgorithmTags.SAFER: + keySize = 128; + algName = "SAFER"; + break; + case SymmetricKeyAlgorithmTags.DES: + keySize = 64; + algName = "DES"; + break; + case SymmetricKeyAlgorithmTags.AES_128: + keySize = 128; + algName = "AES"; + break; + case SymmetricKeyAlgorithmTags.AES_192: + keySize = 192; + algName = "AES"; + break; + case SymmetricKeyAlgorithmTags.AES_256: + keySize = 256; + algName = "AES"; + break; + case SymmetricKeyAlgorithmTags.TWOFISH: + keySize = 256; + algName = "Twofish"; + break; + case SymmetricKeyAlgorithmTags.CAMELLIA_128: + keySize = 128; + algName = "Camellia"; + break; + case SymmetricKeyAlgorithmTags.CAMELLIA_192: + keySize = 192; + algName = "Camellia"; + break; + case SymmetricKeyAlgorithmTags.CAMELLIA_256: + keySize = 256; + algName = "Camellia"; + break; + default: + throw new PGPException("unknown symmetric algorithm: " + algorithm); + } + + byte[] pBytes = Strings.toUTF8ByteArray(passPhrase); + byte[] keyBytes = new byte[(keySize + 7) / 8]; + + int generatedBytes = 0; + int loopCount = 0; + + if (s2k != null) + { + if (s2k.getHashAlgorithm() != digestCalculator.getAlgorithm()) + { + throw new PGPException("s2k/digestCalculator mismatch"); + } + } + else + { + if (digestCalculator.getAlgorithm() != HashAlgorithmTags.MD5) + { + throw new PGPException("digestCalculator not for MD5"); + } + } + + OutputStream dOut = digestCalculator.getOutputStream(); + + try + { + while (generatedBytes < keyBytes.length) + { + if (s2k != null) + { + for (int i = 0; i != loopCount; i++) + { + dOut.write(0); + } + + byte[] iv = s2k.getIV(); + + switch (s2k.getType()) + { + case S2K.SIMPLE: + dOut.write(pBytes); + break; + case S2K.SALTED: + dOut.write(iv); + dOut.write(pBytes); + break; + case S2K.SALTED_AND_ITERATED: + long count = s2k.getIterationCount(); + dOut.write(iv); + dOut.write(pBytes); + + count -= iv.length + pBytes.length; + + while (count > 0) + { + if (count < iv.length) + { + dOut.write(iv, 0, (int)count); + break; + } + else + { + dOut.write(iv); + count -= iv.length; + } + + if (count < pBytes.length) + { + dOut.write(pBytes, 0, (int)count); + count = 0; + } + else + { + dOut.write(pBytes); + count -= pBytes.length; + } + } + break; + default: + throw new PGPException("unknown S2K type: " + s2k.getType()); + } + } + else + { + for (int i = 0; i != loopCount; i++) + { + dOut.write((byte)0); + } + + dOut.write(pBytes); + } + + dOut.close(); + + byte[] dig = digestCalculator.getDigest(); + + if (dig.length > (keyBytes.length - generatedBytes)) + { + System.arraycopy(dig, 0, keyBytes, generatedBytes, keyBytes.length - generatedBytes); + } + else + { + System.arraycopy(dig, 0, keyBytes, generatedBytes, dig.length); + } + + generatedBytes += dig.length; + + loopCount++; + } + } + catch (IOException e) + { + throw new PGPException("exception calculating digest: " + e.getMessage(), e); + } + + for (int i = 0; i != pBytes.length; i++) + { + pBytes[i] = 0; + } + + return keyBytes; + } + + public static byte[] makeKeyFromPassPhrase( + PGPDigestCalculatorProvider digCalcProvider, + int algorithm, + S2K s2k, + char[] passPhrase) + throws PGPException + { + PGPDigestCalculator digestCalculator; + + if (s2k != null) + { + digestCalculator = digCalcProvider.get(s2k.getHashAlgorithm()); + } + else + { + digestCalculator = digCalcProvider.get(HashAlgorithmTags.MD5); + } + + return makeKeyFromPassPhrase(digestCalculator, algorithm, s2k, passPhrase); + } +} diff --git a/pg/src/main/java/org/spongycastle/openpgp/operator/PublicKeyDataDecryptorFactory.java b/pg/src/main/java/org/spongycastle/openpgp/operator/PublicKeyDataDecryptorFactory.java new file mode 100644 index 00000000..35d2a01d --- /dev/null +++ b/pg/src/main/java/org/spongycastle/openpgp/operator/PublicKeyDataDecryptorFactory.java @@ -0,0 +1,10 @@ +package org.spongycastle.openpgp.operator; + +import org.spongycastle.openpgp.PGPException; + +public interface PublicKeyDataDecryptorFactory + extends PGPDataDecryptorFactory +{ + public byte[] recoverSessionData(int keyAlgorithm, byte[][] secKeyData) + throws PGPException; +} diff --git a/pg/src/main/java/org/spongycastle/openpgp/operator/PublicKeyKeyEncryptionMethodGenerator.java b/pg/src/main/java/org/spongycastle/openpgp/operator/PublicKeyKeyEncryptionMethodGenerator.java new file mode 100644 index 00000000..8030b946 --- /dev/null +++ b/pg/src/main/java/org/spongycastle/openpgp/operator/PublicKeyKeyEncryptionMethodGenerator.java @@ -0,0 +1,100 @@ +package org.spongycastle.openpgp.operator; + +import java.io.IOException; +import java.math.BigInteger; + +import org.spongycastle.bcpg.ContainedPacket; +import org.spongycastle.bcpg.MPInteger; +import org.spongycastle.bcpg.PublicKeyEncSessionPacket; +import org.spongycastle.openpgp.PGPException; +import org.spongycastle.openpgp.PGPPublicKey; + +public abstract class PublicKeyKeyEncryptionMethodGenerator + extends PGPKeyEncryptionMethodGenerator +{ + private PGPPublicKey pubKey; + + protected PublicKeyKeyEncryptionMethodGenerator( + PGPPublicKey pubKey) + { + this.pubKey = pubKey; + + switch (pubKey.getAlgorithm()) + { + case PGPPublicKey.RSA_ENCRYPT: + case PGPPublicKey.RSA_GENERAL: + break; + case PGPPublicKey.ELGAMAL_ENCRYPT: + case PGPPublicKey.ELGAMAL_GENERAL: + break; + case PGPPublicKey.ECDH: + break; + case PGPPublicKey.DSA: + throw new IllegalArgumentException("Can't use DSA for encryption."); + case PGPPublicKey.ECDSA: + throw new IllegalArgumentException("Can't use ECDSA for encryption."); + default: + throw new IllegalArgumentException("unknown asymmetric algorithm: " + pubKey.getAlgorithm()); + } + } + + public byte[][] processSessionInfo( + byte[] encryptedSessionInfo) + throws PGPException + { + byte[][] data; + + switch (pubKey.getAlgorithm()) + { + case PGPPublicKey.RSA_ENCRYPT: + case PGPPublicKey.RSA_GENERAL: + data = new byte[1][]; + + data[0] = convertToEncodedMPI(encryptedSessionInfo); + break; + case PGPPublicKey.ELGAMAL_ENCRYPT: + case PGPPublicKey.ELGAMAL_GENERAL: + byte[] b1 = new byte[encryptedSessionInfo.length / 2]; + byte[] b2 = new byte[encryptedSessionInfo.length / 2]; + + System.arraycopy(encryptedSessionInfo, 0, b1, 0, b1.length); + System.arraycopy(encryptedSessionInfo, b1.length, b2, 0, b2.length); + + data = new byte[2][]; + data[0] = convertToEncodedMPI(b1); + data[1] = convertToEncodedMPI(b2); + break; + case PGPPublicKey.ECDH: + data = new byte[1][]; + + data[0] = encryptedSessionInfo; + break; + default: + throw new PGPException("unknown asymmetric algorithm: " + pubKey.getAlgorithm()); + } + + return data; + } + + private byte[] convertToEncodedMPI(byte[] encryptedSessionInfo) + throws PGPException + { + try + { + return new MPInteger(new BigInteger(1, encryptedSessionInfo)).getEncoded(); + } + catch (IOException e) + { + throw new PGPException("Invalid MPI encoding: " + e.getMessage(), e); + } + } + + public ContainedPacket generate(int encAlgorithm, byte[] sessionInfo) + throws PGPException + { + return new PublicKeyEncSessionPacket(pubKey.getKeyID(), pubKey.getAlgorithm(), processSessionInfo(encryptSessionInfo(pubKey, sessionInfo))); + } + + abstract protected byte[] encryptSessionInfo(PGPPublicKey pubKey, byte[] sessionInfo) + throws PGPException; +} diff --git a/pg/src/main/java/org/spongycastle/openpgp/operator/RFC6637KDFCalculator.java b/pg/src/main/java/org/spongycastle/openpgp/operator/RFC6637KDFCalculator.java new file mode 100644 index 00000000..bc342f68 --- /dev/null +++ b/pg/src/main/java/org/spongycastle/openpgp/operator/RFC6637KDFCalculator.java @@ -0,0 +1,115 @@ +package org.spongycastle.openpgp.operator; + +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.OutputStream; + +import org.spongycastle.asn1.ASN1ObjectIdentifier; +import org.spongycastle.bcpg.PublicKeyAlgorithmTags; +import org.spongycastle.bcpg.SymmetricKeyAlgorithmTags; +import org.spongycastle.math.ec.ECPoint; +import org.spongycastle.openpgp.PGPException; +import org.spongycastle.util.encoders.Hex; + +/** + * Calculator for the EC based KDF algorithm described in RFC 6637 + */ +public class RFC6637KDFCalculator +{ + // "Anonymous Sender ", which is the octet sequence + private static final byte[] ANONYMOUS_SENDER = Hex.decode("416E6F6E796D6F75732053656E64657220202020"); + + private final PGPDigestCalculator digCalc; + private final int keyAlgorithm; + + public RFC6637KDFCalculator(PGPDigestCalculator digCalc, int keyAlgorithm) + { + this.digCalc = digCalc; + this.keyAlgorithm = keyAlgorithm; + } + + public byte[] createKey(ASN1ObjectIdentifier curveOID, ECPoint s, byte[] recipientFingerPrint) + throws PGPException + { + try + { + // RFC 6637 - Section 8 + // curve_OID_len = (byte)len(curve_OID); + // Param = curve_OID_len || curve_OID || public_key_alg_ID || 03 + // || 01 || KDF_hash_ID || KEK_alg_ID for AESKeyWrap || "Anonymous + // Sender " || recipient_fingerprint; + // Z_len = the key size for the KEK_alg_ID used with AESKeyWrap + // Compute Z = KDF( S, Z_len, Param ); + ByteArrayOutputStream pOut = new ByteArrayOutputStream(); + + byte[] encOid = curveOID.getEncoded(); + + pOut.write(encOid, 1, encOid.length - 1); + pOut.write(PublicKeyAlgorithmTags.ECDH); + pOut.write(0x03); + pOut.write(0x01); + pOut.write(digCalc.getAlgorithm()); + pOut.write(keyAlgorithm); + pOut.write(ANONYMOUS_SENDER); + pOut.write(recipientFingerPrint); + + return KDF(digCalc, s, getKeyLen(keyAlgorithm), pOut.toByteArray()); + } + catch (IOException e) + { + throw new PGPException("Exception performing KDF: " + e.getMessage(), e); + } + } + + // RFC 6637 - Section 7 + // Implements KDF( X, oBits, Param ); + // Input: point X = (x,y) + // oBits - the desired size of output + // hBits - the size of output of hash function Hash + // Param - octets representing the parameters + // Assumes that oBits <= hBits + // Convert the point X to the octet string, see section 6: + // ZB' = 04 || x || y + // and extract the x portion from ZB' + // ZB = x; + // MB = Hash ( 00 || 00 || 00 || 01 || ZB || Param ); + // return oBits leftmost bits of MB. + private static byte[] KDF(PGPDigestCalculator digCalc, ECPoint s, int keyLen, byte[] param) + throws IOException + { + byte[] ZB = s.getXCoord().getEncoded(); + + OutputStream dOut = digCalc.getOutputStream(); + + dOut.write(0x00); + dOut.write(0x00); + dOut.write(0x00); + dOut.write(0x01); + dOut.write(ZB); + dOut.write(param); + + byte[] digest = digCalc.getDigest(); + + byte[] key = new byte[keyLen]; + + System.arraycopy(digest, 0, key, 0, key.length); + + return key; + } + + private static int getKeyLen(int algID) + throws PGPException + { + switch (algID) + { + case SymmetricKeyAlgorithmTags.AES_128: + return 16; + case SymmetricKeyAlgorithmTags.AES_192: + return 24; + case SymmetricKeyAlgorithmTags.AES_256: + return 32; + default: + throw new PGPException("unknown symmetric algorithm ID: " + algID); + } + } +} diff --git a/pg/src/main/java/org/spongycastle/openpgp/operator/bc/BcImplProvider.java b/pg/src/main/java/org/spongycastle/openpgp/operator/bc/BcImplProvider.java new file mode 100644 index 00000000..f54870f0 --- /dev/null +++ b/pg/src/main/java/org/spongycastle/openpgp/operator/bc/BcImplProvider.java @@ -0,0 +1,174 @@ +package org.spongycastle.openpgp.operator.bc; + +import org.spongycastle.bcpg.HashAlgorithmTags; +import org.spongycastle.bcpg.PublicKeyAlgorithmTags; +import org.spongycastle.bcpg.SymmetricKeyAlgorithmTags; +import org.spongycastle.crypto.AsymmetricBlockCipher; +import org.spongycastle.crypto.BlockCipher; +import org.spongycastle.crypto.Digest; +import org.spongycastle.crypto.Signer; +import org.spongycastle.crypto.Wrapper; +import org.spongycastle.crypto.digests.MD2Digest; +import org.spongycastle.crypto.digests.MD5Digest; +import org.spongycastle.crypto.digests.RIPEMD160Digest; +import org.spongycastle.crypto.digests.SHA1Digest; +import org.spongycastle.crypto.digests.SHA224Digest; +import org.spongycastle.crypto.digests.SHA256Digest; +import org.spongycastle.crypto.digests.SHA384Digest; +import org.spongycastle.crypto.digests.SHA512Digest; +import org.spongycastle.crypto.digests.TigerDigest; +import org.spongycastle.crypto.encodings.PKCS1Encoding; +import org.spongycastle.crypto.engines.AESEngine; +import org.spongycastle.crypto.engines.AESFastEngine; +import org.spongycastle.crypto.engines.BlowfishEngine; +import org.spongycastle.crypto.engines.CAST5Engine; +import org.spongycastle.crypto.engines.CamelliaEngine; +import org.spongycastle.crypto.engines.DESEngine; +import org.spongycastle.crypto.engines.DESedeEngine; +import org.spongycastle.crypto.engines.ElGamalEngine; +import org.spongycastle.crypto.engines.IDEAEngine; +import org.spongycastle.crypto.engines.RFC3394WrapEngine; +import org.spongycastle.crypto.engines.RSABlindedEngine; +import org.spongycastle.crypto.engines.TwofishEngine; +import org.spongycastle.crypto.signers.DSADigestSigner; +import org.spongycastle.crypto.signers.DSASigner; +import org.spongycastle.crypto.signers.ECDSASigner; +import org.spongycastle.crypto.signers.RSADigestSigner; +import org.spongycastle.openpgp.PGPException; +import org.spongycastle.openpgp.PGPPublicKey; + +class BcImplProvider +{ + static Digest createDigest(int algorithm) + throws PGPException + { + switch (algorithm) + { + case HashAlgorithmTags.SHA1: + return new SHA1Digest(); + case HashAlgorithmTags.SHA224: + return new SHA224Digest(); + case HashAlgorithmTags.SHA256: + return new SHA256Digest(); + case HashAlgorithmTags.SHA384: + return new SHA384Digest(); + case HashAlgorithmTags.SHA512: + return new SHA512Digest(); + case HashAlgorithmTags.MD2: + return new MD2Digest(); + case HashAlgorithmTags.MD5: + return new MD5Digest(); + case HashAlgorithmTags.RIPEMD160: + return new RIPEMD160Digest(); + case HashAlgorithmTags.TIGER_192: + return new TigerDigest(); + default: + throw new PGPException("cannot recognise digest"); + } + } + + static Signer createSigner(int keyAlgorithm, int hashAlgorithm) + throws PGPException + { + switch(keyAlgorithm) + { + case PublicKeyAlgorithmTags.RSA_GENERAL: + case PublicKeyAlgorithmTags.RSA_SIGN: + return new RSADigestSigner(createDigest(hashAlgorithm)); + case PublicKeyAlgorithmTags.DSA: + return new DSADigestSigner(new DSASigner(), createDigest(hashAlgorithm)); + case PublicKeyAlgorithmTags.ECDSA: + return new DSADigestSigner(new ECDSASigner(), createDigest(hashAlgorithm)); + default: + throw new PGPException("cannot recognise keyAlgorithm: " + keyAlgorithm); + } + } + + static BlockCipher createBlockCipher(int encAlgorithm) + throws PGPException + { + BlockCipher engine; + + switch (encAlgorithm) + { + case SymmetricKeyAlgorithmTags.AES_128: + case SymmetricKeyAlgorithmTags.AES_192: + case SymmetricKeyAlgorithmTags.AES_256: + engine = new AESEngine(); + break; + case SymmetricKeyAlgorithmTags.CAMELLIA_128: + case SymmetricKeyAlgorithmTags.CAMELLIA_192: + case SymmetricKeyAlgorithmTags.CAMELLIA_256: + engine = new CamelliaEngine(); + break; + case SymmetricKeyAlgorithmTags.BLOWFISH: + engine = new BlowfishEngine(); + break; + case SymmetricKeyAlgorithmTags.CAST5: + engine = new CAST5Engine(); + break; + case SymmetricKeyAlgorithmTags.DES: + engine = new DESEngine(); + break; + case SymmetricKeyAlgorithmTags.IDEA: + engine = new IDEAEngine(); + break; + case SymmetricKeyAlgorithmTags.TWOFISH: + engine = new TwofishEngine(); + break; + case SymmetricKeyAlgorithmTags.TRIPLE_DES: + engine = new DESedeEngine(); + break; + default: + throw new PGPException("cannot recognise cipher"); + } + + return engine; + } + + static Wrapper createWrapper(int encAlgorithm) + throws PGPException + { + switch (encAlgorithm) + { + case SymmetricKeyAlgorithmTags.AES_128: + case SymmetricKeyAlgorithmTags.AES_192: + case SymmetricKeyAlgorithmTags.AES_256: + return new RFC3394WrapEngine(new AESFastEngine()); + case SymmetricKeyAlgorithmTags.CAMELLIA_128: + case SymmetricKeyAlgorithmTags.CAMELLIA_192: + case SymmetricKeyAlgorithmTags.CAMELLIA_256: + return new RFC3394WrapEngine(new CamelliaEngine()); + default: + throw new PGPException("unknown wrap algorithm: " + encAlgorithm); + } + } + + static AsymmetricBlockCipher createPublicKeyCipher(int encAlgorithm) + throws PGPException + { + AsymmetricBlockCipher c; + + switch (encAlgorithm) + { + case PGPPublicKey.RSA_ENCRYPT: + case PGPPublicKey.RSA_GENERAL: + c = new PKCS1Encoding(new RSABlindedEngine()); + break; + case PGPPublicKey.ELGAMAL_ENCRYPT: + case PGPPublicKey.ELGAMAL_GENERAL: + c = new PKCS1Encoding(new ElGamalEngine()); + break; + case PGPPublicKey.DSA: + throw new PGPException("Can't use DSA for encryption."); + case PGPPublicKey.ECDSA: + throw new PGPException("Can't use ECDSA for encryption."); + case PGPPublicKey.ECDH: + throw new PGPException("Not implemented."); + default: + throw new PGPException("unknown asymmetric algorithm: " + encAlgorithm); + } + + return c; + } +} diff --git a/pg/src/main/java/org/spongycastle/openpgp/operator/bc/BcKeyFingerprintCalculator.java b/pg/src/main/java/org/spongycastle/openpgp/operator/bc/BcKeyFingerprintCalculator.java new file mode 100644 index 00000000..9b9b7f26 --- /dev/null +++ b/pg/src/main/java/org/spongycastle/openpgp/operator/bc/BcKeyFingerprintCalculator.java @@ -0,0 +1,68 @@ +package org.spongycastle.openpgp.operator.bc; + +import java.io.IOException; + +import org.spongycastle.bcpg.BCPGKey; +import org.spongycastle.bcpg.MPInteger; +import org.spongycastle.bcpg.PublicKeyPacket; +import org.spongycastle.bcpg.RSAPublicBCPGKey; +import org.spongycastle.crypto.Digest; +import org.spongycastle.crypto.digests.MD5Digest; +import org.spongycastle.crypto.digests.SHA1Digest; +import org.spongycastle.openpgp.PGPException; +import org.spongycastle.openpgp.operator.KeyFingerPrintCalculator; + +public class BcKeyFingerprintCalculator + implements KeyFingerPrintCalculator +{ + public byte[] calculateFingerprint(PublicKeyPacket publicPk) + throws PGPException + { + BCPGKey key = publicPk.getKey(); + Digest digest; + + if (publicPk.getVersion() <= 3) + { + RSAPublicBCPGKey rK = (RSAPublicBCPGKey)key; + + try + { + digest = new MD5Digest(); + + byte[] bytes = new MPInteger(rK.getModulus()).getEncoded(); + digest.update(bytes, 2, bytes.length - 2); + + bytes = new MPInteger(rK.getPublicExponent()).getEncoded(); + digest.update(bytes, 2, bytes.length - 2); + } + catch (IOException e) + { + throw new PGPException("can't encode key components: " + e.getMessage(), e); + } + } + else + { + try + { + byte[] kBytes = publicPk.getEncodedContents(); + + digest = new SHA1Digest(); + + digest.update((byte)0x99); + digest.update((byte)(kBytes.length >> 8)); + digest.update((byte)kBytes.length); + digest.update(kBytes, 0, kBytes.length); + } + catch (IOException e) + { + throw new PGPException("can't encode key components: " + e.getMessage(), e); + } + } + + byte[] digBuf = new byte[digest.getDigestSize()]; + + digest.doFinal(digBuf, 0); + + return digBuf; + } +} diff --git a/pg/src/main/java/org/spongycastle/openpgp/operator/bc/BcPBEDataDecryptorFactory.java b/pg/src/main/java/org/spongycastle/openpgp/operator/bc/BcPBEDataDecryptorFactory.java new file mode 100644 index 00000000..b9a1c189 --- /dev/null +++ b/pg/src/main/java/org/spongycastle/openpgp/operator/bc/BcPBEDataDecryptorFactory.java @@ -0,0 +1,68 @@ +package org.spongycastle.openpgp.operator.bc; + +import org.spongycastle.crypto.BlockCipher; +import org.spongycastle.crypto.BufferedBlockCipher; +import org.spongycastle.openpgp.PGPException; +import org.spongycastle.openpgp.operator.PBEDataDecryptorFactory; +import org.spongycastle.openpgp.operator.PGPDataDecryptor; + +/** + * A {@link PBEDataDecryptorFactory} for handling PBE decryption operations using the Bouncy Castle + * lightweight API to implement cryptographic primitives. + */ +public class BcPBEDataDecryptorFactory + extends PBEDataDecryptorFactory +{ + /** + * Base constructor. + * + * @param pass the passphrase to use as the primary source of key material. + * @param calculatorProvider a digest calculator provider to provide calculators to support the key generation calculation required. + */ + public BcPBEDataDecryptorFactory(char[] pass, BcPGPDigestCalculatorProvider calculatorProvider) + { + super(pass, calculatorProvider); + } + + public byte[] recoverSessionData(int keyAlgorithm, byte[] key, byte[] secKeyData) + throws PGPException + { + try + { + if (secKeyData != null && secKeyData.length > 0) + { + BlockCipher engine = BcImplProvider.createBlockCipher(keyAlgorithm); + BufferedBlockCipher cipher = BcUtil.createSymmetricKeyWrapper(false, engine, key, new byte[engine.getBlockSize()]); + + byte[] out = new byte[secKeyData.length]; + + int len = cipher.processBytes(secKeyData, 0, secKeyData.length, out, 0); + + len += cipher.doFinal(out, len); + + return out; + } + else + { + byte[] keyBytes = new byte[key.length + 1]; + + keyBytes[0] = (byte)keyAlgorithm; + System.arraycopy(key, 0, keyBytes, 1, key.length); + + return keyBytes; + } + } + catch (Exception e) + { + throw new PGPException("Exception recovering session info", e); + } + } + + public PGPDataDecryptor createDataDecryptor(boolean withIntegrityPacket, int encAlgorithm, byte[] key) + throws PGPException + { + BlockCipher engine = BcImplProvider.createBlockCipher(encAlgorithm); + + return BcUtil.createDataDecryptor(withIntegrityPacket, engine, key); + } +} diff --git a/pg/src/main/java/org/spongycastle/openpgp/operator/bc/BcPBEKeyEncryptionMethodGenerator.java b/pg/src/main/java/org/spongycastle/openpgp/operator/bc/BcPBEKeyEncryptionMethodGenerator.java new file mode 100644 index 00000000..2a151f0d --- /dev/null +++ b/pg/src/main/java/org/spongycastle/openpgp/operator/bc/BcPBEKeyEncryptionMethodGenerator.java @@ -0,0 +1,95 @@ +package org.spongycastle.openpgp.operator.bc; + +import java.security.SecureRandom; + +import org.spongycastle.bcpg.S2K; +import org.spongycastle.crypto.BlockCipher; +import org.spongycastle.crypto.BufferedBlockCipher; +import org.spongycastle.crypto.InvalidCipherTextException; +import org.spongycastle.openpgp.PGPException; +import org.spongycastle.openpgp.operator.PBEKeyEncryptionMethodGenerator; +import org.spongycastle.openpgp.operator.PGPDigestCalculator; + +/** + * A BC lightweight method generator for supporting PBE based encryption operations. + */ +public class BcPBEKeyEncryptionMethodGenerator + extends PBEKeyEncryptionMethodGenerator +{ + /** + * Create a PBE encryption method generator using the provided digest and the default S2K count + * for key generation. + * + * @param passPhrase the passphrase to use as the primary source of key material. + * @param s2kDigestCalculator the digest calculator to use for key calculation. + */ + public BcPBEKeyEncryptionMethodGenerator(char[] passPhrase, PGPDigestCalculator s2kDigestCalculator) + { + super(passPhrase, s2kDigestCalculator); + } + + /** + * Create a PBE encryption method generator using the default SHA-1 digest and the default S2K + * count for key generation. + * + * @param passPhrase the passphrase to use as the primary source of key material. + */ + public BcPBEKeyEncryptionMethodGenerator(char[] passPhrase) + { + this(passPhrase, new SHA1PGPDigestCalculator()); + } + + /** + * Create a PBE encryption method generator using the provided calculator and S2K count for key + * generation. + * + * @param passPhrase the passphrase to use as the primary source of key material. + * @param s2kDigestCalculator the digest calculator to use for key calculation. + * @param s2kCount the single byte {@link S2K} count to use. + */ + public BcPBEKeyEncryptionMethodGenerator(char[] passPhrase, PGPDigestCalculator s2kDigestCalculator, int s2kCount) + { + super(passPhrase, s2kDigestCalculator, s2kCount); + } + + /** + * Create a PBE encryption method generator using the default SHA-1 digest calculator and a S2K + * count other than the default for key generation. + * + * @param passPhrase the passphrase to use as the primary source of key material. + * @param s2kCount the single byte {@link S2K} count to use. + */ + public BcPBEKeyEncryptionMethodGenerator(char[] passPhrase, int s2kCount) + { + super(passPhrase, new SHA1PGPDigestCalculator(), s2kCount); + } + + public PBEKeyEncryptionMethodGenerator setSecureRandom(SecureRandom random) + { + super.setSecureRandom(random); + + return this; + } + + protected byte[] encryptSessionInfo(int encAlgorithm, byte[] key, byte[] sessionInfo) + throws PGPException + { + try + { + BlockCipher engine = BcImplProvider.createBlockCipher(encAlgorithm); + BufferedBlockCipher cipher = BcUtil.createSymmetricKeyWrapper(true, engine, key, new byte[engine.getBlockSize()]); + + byte[] out = new byte[sessionInfo.length]; + + int len = cipher.processBytes(sessionInfo, 0, sessionInfo.length, out, 0); + + len += cipher.doFinal(out, len); + + return out; + } + catch (InvalidCipherTextException e) + { + throw new PGPException("encryption failed: " + e.getMessage(), e); + } + } +} diff --git a/pg/src/main/java/org/spongycastle/openpgp/operator/bc/BcPBESecretKeyDecryptorBuilder.java b/pg/src/main/java/org/spongycastle/openpgp/operator/bc/BcPBESecretKeyDecryptorBuilder.java new file mode 100644 index 00000000..bf0a0db9 --- /dev/null +++ b/pg/src/main/java/org/spongycastle/openpgp/operator/bc/BcPBESecretKeyDecryptorBuilder.java @@ -0,0 +1,43 @@ +package org.spongycastle.openpgp.operator.bc; + +import org.spongycastle.crypto.BufferedBlockCipher; +import org.spongycastle.crypto.InvalidCipherTextException; +import org.spongycastle.openpgp.PGPException; +import org.spongycastle.openpgp.operator.PBESecretKeyDecryptor; +import org.spongycastle.openpgp.operator.PGPDigestCalculatorProvider; + +public class BcPBESecretKeyDecryptorBuilder +{ + private PGPDigestCalculatorProvider calculatorProvider; + + public BcPBESecretKeyDecryptorBuilder(PGPDigestCalculatorProvider calculatorProvider) + { + this.calculatorProvider = calculatorProvider; + } + + public PBESecretKeyDecryptor build(char[] passPhrase) + { + return new PBESecretKeyDecryptor(passPhrase, calculatorProvider) + { + public byte[] recoverKeyData(int encAlgorithm, byte[] key, byte[] iv, byte[] keyData, int keyOff, int keyLen) + throws PGPException + { + try + { + BufferedBlockCipher c = BcUtil.createSymmetricKeyWrapper(false, BcImplProvider.createBlockCipher(encAlgorithm), key, iv); + + byte[] out = new byte[keyLen]; + int outLen = c.processBytes(keyData, keyOff, keyLen, out, 0); + + outLen += c.doFinal(out, outLen); + + return out; + } + catch (InvalidCipherTextException e) + { + throw new PGPException("decryption failed: " + e.getMessage(), e); + } + } + }; + } +} diff --git a/pg/src/main/java/org/spongycastle/openpgp/operator/bc/BcPBESecretKeyEncryptorBuilder.java b/pg/src/main/java/org/spongycastle/openpgp/operator/bc/BcPBESecretKeyEncryptorBuilder.java new file mode 100644 index 00000000..aea664ae --- /dev/null +++ b/pg/src/main/java/org/spongycastle/openpgp/operator/bc/BcPBESecretKeyEncryptorBuilder.java @@ -0,0 +1,142 @@ +package org.spongycastle.openpgp.operator.bc; + +import java.security.SecureRandom; + +import org.spongycastle.crypto.BlockCipher; +import org.spongycastle.crypto.BufferedBlockCipher; +import org.spongycastle.crypto.InvalidCipherTextException; +import org.spongycastle.openpgp.PGPException; +import org.spongycastle.openpgp.operator.PBESecretKeyEncryptor; +import org.spongycastle.openpgp.operator.PGPDigestCalculator; + +public class BcPBESecretKeyEncryptorBuilder +{ + private int encAlgorithm; + private PGPDigestCalculator s2kDigestCalculator; + private SecureRandom random; + private int s2kCount = 0x60; + + public BcPBESecretKeyEncryptorBuilder(int encAlgorithm) + { + this(encAlgorithm, new SHA1PGPDigestCalculator()); + } + + /** + * Create an SecretKeyEncryptorBuilder with the S2K count different to the default of 0x60. + * + * @param encAlgorithm encryption algorithm to use. + * @param s2kCount iteration count to use for S2K function. + */ + public BcPBESecretKeyEncryptorBuilder(int encAlgorithm, int s2kCount) + { + this(encAlgorithm, new SHA1PGPDigestCalculator(), s2kCount); + } + + /** + * Create a builder which will make encryptors using the passed in digest calculator. If a MD5 calculator is + * passed in the builder will assume the encryptors are for use with version 3 keys. + * + * @param encAlgorithm encryption algorithm to use. + * @param s2kDigestCalculator digest calculator to use. + */ + public BcPBESecretKeyEncryptorBuilder(int encAlgorithm, PGPDigestCalculator s2kDigestCalculator) + { + this(encAlgorithm, s2kDigestCalculator, 0x60); + } + + /** + * Create an SecretKeyEncryptorBuilder with the S2k count different to the default of 0x60, and the S2K digest + * different from SHA-1. + * + * @param encAlgorithm encryption algorithm to use. + * @param s2kDigestCalculator digest calculator to use. + * @param s2kCount iteration count to use for S2K function. + */ + public BcPBESecretKeyEncryptorBuilder(int encAlgorithm, PGPDigestCalculator s2kDigestCalculator, int s2kCount) + { + this.encAlgorithm = encAlgorithm; + this.s2kDigestCalculator = s2kDigestCalculator; + + if (s2kCount < 0 || s2kCount > 0xff) + { + throw new IllegalArgumentException("s2KCount value outside of range 0 to 255."); + } + + this.s2kCount = s2kCount; + } + + /** + * Provide a user defined source of randomness. + * + * @param random the secure random to be used. + * @return the current builder. + */ + public BcPBESecretKeyEncryptorBuilder setSecureRandom(SecureRandom random) + { + this.random = random; + + return this; + } + + public PBESecretKeyEncryptor build(char[] passPhrase) + { + if (this.random == null) + { + this.random = new SecureRandom(); + } + + return new PBESecretKeyEncryptor(encAlgorithm, s2kDigestCalculator, s2kCount, this.random, passPhrase) + { + private byte[] iv; + + public byte[] encryptKeyData(byte[] key, byte[] keyData, int keyOff, int keyLen) + throws PGPException + { + return encryptKeyData(key, null, keyData, keyOff, keyLen); + } + + public byte[] encryptKeyData(byte[] key, byte[] iv, byte[] keyData, int keyOff, int keyLen) + throws PGPException + { + try + { + BlockCipher engine = BcImplProvider.createBlockCipher(this.encAlgorithm); + + if (iv != null) + { // to deal with V3 key encryption + this.iv = iv; + } + else + { + if (this.random == null) + { + this.random = new SecureRandom(); + } + + this.iv = iv = new byte[engine.getBlockSize()]; + + this.random.nextBytes(iv); + } + + BufferedBlockCipher c = BcUtil.createSymmetricKeyWrapper(true, engine, key, iv); + + byte[] out = new byte[keyLen]; + int outLen = c.processBytes(keyData, keyOff, keyLen, out, 0); + + outLen += c.doFinal(out, outLen); + + return out; + } + catch (InvalidCipherTextException e) + { + throw new PGPException("decryption failed: " + e.getMessage(), e); + } + } + + public byte[] getCipherIV() + { + return iv; + } + }; + } +} diff --git a/pg/src/main/java/org/spongycastle/openpgp/operator/bc/BcPGPContentSignerBuilder.java b/pg/src/main/java/org/spongycastle/openpgp/operator/bc/BcPGPContentSignerBuilder.java new file mode 100644 index 00000000..cd98ef38 --- /dev/null +++ b/pg/src/main/java/org/spongycastle/openpgp/operator/bc/BcPGPContentSignerBuilder.java @@ -0,0 +1,98 @@ +package org.spongycastle.openpgp.operator.bc; + +import java.io.OutputStream; +import java.security.SecureRandom; + +import org.spongycastle.crypto.CryptoException; +import org.spongycastle.crypto.Signer; +import org.spongycastle.crypto.params.ParametersWithRandom; +import org.spongycastle.openpgp.PGPException; +import org.spongycastle.openpgp.PGPPrivateKey; +import org.spongycastle.openpgp.operator.PGPContentSigner; +import org.spongycastle.openpgp.operator.PGPContentSignerBuilder; +import org.spongycastle.openpgp.operator.PGPDigestCalculator; +import org.spongycastle.util.io.TeeOutputStream; + +public class BcPGPContentSignerBuilder + implements PGPContentSignerBuilder +{ + private BcPGPDigestCalculatorProvider digestCalculatorProvider = new BcPGPDigestCalculatorProvider(); + private BcPGPKeyConverter keyConverter = new BcPGPKeyConverter(); + private int hashAlgorithm; + private SecureRandom random; + private int keyAlgorithm; + + public BcPGPContentSignerBuilder(int keyAlgorithm, int hashAlgorithm) + { + this.keyAlgorithm = keyAlgorithm; + this.hashAlgorithm = hashAlgorithm; + } + + public BcPGPContentSignerBuilder setSecureRandom(SecureRandom random) + { + this.random = random; + + return this; + } + + public PGPContentSigner build(final int signatureType, final PGPPrivateKey privateKey) + throws PGPException + { + final PGPDigestCalculator digestCalculator = digestCalculatorProvider.get(hashAlgorithm); + final Signer signer = BcImplProvider.createSigner(keyAlgorithm, hashAlgorithm); + + if (random != null) + { + signer.init(true, new ParametersWithRandom(keyConverter.getPrivateKey(privateKey), random)); + } + else + { + signer.init(true, keyConverter.getPrivateKey(privateKey)); + } + + return new PGPContentSigner() + { + public int getType() + { + return signatureType; + } + + public int getHashAlgorithm() + { + return hashAlgorithm; + } + + public int getKeyAlgorithm() + { + return keyAlgorithm; + } + + public long getKeyID() + { + return privateKey.getKeyID(); + } + + public OutputStream getOutputStream() + { + return new TeeOutputStream(new SignerOutputStream(signer), digestCalculator.getOutputStream()); + } + + public byte[] getSignature() + { + try + { + return signer.generateSignature(); + } + catch (CryptoException e) + { // TODO: need a specific runtime exception for PGP operators. + throw new IllegalStateException("unable to create signature"); + } + } + + public byte[] getDigest() + { + return digestCalculator.getDigest(); + } + }; + } +} diff --git a/pg/src/main/java/org/spongycastle/openpgp/operator/bc/BcPGPContentVerifierBuilderProvider.java b/pg/src/main/java/org/spongycastle/openpgp/operator/bc/BcPGPContentVerifierBuilderProvider.java new file mode 100644 index 00000000..a2cfbf91 --- /dev/null +++ b/pg/src/main/java/org/spongycastle/openpgp/operator/bc/BcPGPContentVerifierBuilderProvider.java @@ -0,0 +1,75 @@ +package org.spongycastle.openpgp.operator.bc; + +import java.io.OutputStream; + +import org.spongycastle.crypto.Signer; +import org.spongycastle.openpgp.PGPException; +import org.spongycastle.openpgp.PGPPublicKey; +import org.spongycastle.openpgp.operator.PGPContentVerifier; +import org.spongycastle.openpgp.operator.PGPContentVerifierBuilder; +import org.spongycastle.openpgp.operator.PGPContentVerifierBuilderProvider; + +public class BcPGPContentVerifierBuilderProvider + implements PGPContentVerifierBuilderProvider +{ + private BcPGPKeyConverter keyConverter = new BcPGPKeyConverter(); + + public BcPGPContentVerifierBuilderProvider() + { + } + + public PGPContentVerifierBuilder get(int keyAlgorithm, int hashAlgorithm) + throws PGPException + { + return new BcPGPContentVerifierBuilder(keyAlgorithm, hashAlgorithm); + } + + private class BcPGPContentVerifierBuilder + implements PGPContentVerifierBuilder + { + private int hashAlgorithm; + private int keyAlgorithm; + + public BcPGPContentVerifierBuilder(int keyAlgorithm, int hashAlgorithm) + { + this.keyAlgorithm = keyAlgorithm; + this.hashAlgorithm = hashAlgorithm; + } + + public PGPContentVerifier build(final PGPPublicKey publicKey) + throws PGPException + { + final Signer signer = BcImplProvider.createSigner(keyAlgorithm, hashAlgorithm); + + signer.init(false, keyConverter.getPublicKey(publicKey)); + + return new PGPContentVerifier() + { + public int getHashAlgorithm() + { + return hashAlgorithm; + } + + public int getKeyAlgorithm() + { + return keyAlgorithm; + } + + public long getKeyID() + { + return publicKey.getKeyID(); + } + + public boolean verify(byte[] expected) + { + return signer.verifySignature(expected); + } + + public OutputStream getOutputStream() + { + return new SignerOutputStream(signer); + } + }; + } + } +} diff --git a/pg/src/main/java/org/spongycastle/openpgp/operator/bc/BcPGPDataEncryptorBuilder.java b/pg/src/main/java/org/spongycastle/openpgp/operator/bc/BcPGPDataEncryptorBuilder.java new file mode 100644 index 00000000..bfb2946a --- /dev/null +++ b/pg/src/main/java/org/spongycastle/openpgp/operator/bc/BcPGPDataEncryptorBuilder.java @@ -0,0 +1,131 @@ +package org.spongycastle.openpgp.operator.bc; + +import java.io.OutputStream; +import java.security.SecureRandom; + +import org.spongycastle.bcpg.SymmetricKeyAlgorithmTags; +import org.spongycastle.crypto.BlockCipher; +import org.spongycastle.crypto.BufferedBlockCipher; +import org.spongycastle.crypto.io.CipherOutputStream; +import org.spongycastle.openpgp.PGPException; +import org.spongycastle.openpgp.operator.PGPDataEncryptor; +import org.spongycastle.openpgp.operator.PGPDataEncryptorBuilder; +import org.spongycastle.openpgp.operator.PGPDigestCalculator; + +/** + * {@link PGPDataEncryptorBuilder} implementation that uses the Bouncy Castle lightweight API to + * implement cryptographic primitives. + */ +public class BcPGPDataEncryptorBuilder + implements PGPDataEncryptorBuilder +{ + private SecureRandom random; + private boolean withIntegrityPacket; + private int encAlgorithm; + + /** + * Constructs a new data encryptor builder for a specified cipher type. + * + * @param encAlgorithm one of the {@link SymmetricKeyAlgorithmTags supported symmetric cipher + * algorithms}. May not be {@link SymmetricKeyAlgorithmTags#NULL}. + */ + public BcPGPDataEncryptorBuilder(int encAlgorithm) + { + this.encAlgorithm = encAlgorithm; + + if (encAlgorithm == 0) + { + throw new IllegalArgumentException("null cipher specified"); + } + } + + /** + * Sets whether or not the resulting encrypted data will be protected using an integrity packet. + * + * @param withIntegrityPacket true if an integrity packet is to be included, false otherwise. + * @return the current builder. + */ + public BcPGPDataEncryptorBuilder setWithIntegrityPacket(boolean withIntegrityPacket) + { + this.withIntegrityPacket = withIntegrityPacket; + + return this; + } + + /** + * Provide a user defined source of randomness. + * <p/> + * If no SecureRandom is configured, a default SecureRandom will be used. + * + * @param random the secure random to be used. + * @return the current builder. + */ + public BcPGPDataEncryptorBuilder setSecureRandom(SecureRandom random) + { + this.random = random; + + return this; + } + + public int getAlgorithm() + { + return encAlgorithm; + } + + public SecureRandom getSecureRandom() + { + if (random == null) + { + random = new SecureRandom(); + } + + return random; + } + + public PGPDataEncryptor build(byte[] keyBytes) + throws PGPException + { + return new MyPGPDataEncryptor(keyBytes); + } + + private class MyPGPDataEncryptor + implements PGPDataEncryptor + { + private final BufferedBlockCipher c; + + MyPGPDataEncryptor(byte[] keyBytes) + throws PGPException + { + BlockCipher engine = BcImplProvider.createBlockCipher(encAlgorithm); + + try + { + c = BcUtil.createStreamCipher(true, engine, withIntegrityPacket, keyBytes); + } + catch (IllegalArgumentException e) + { + throw new PGPException("invalid parameters: " + e.getMessage(), e); + } + } + + public OutputStream getOutputStream(OutputStream out) + { + return new CipherOutputStream(out, c); + } + + public PGPDigestCalculator getIntegrityCalculator() + { + if (withIntegrityPacket) + { + return new SHA1PGPDigestCalculator(); + } + + return null; + } + + public int getBlockSize() + { + return c.getBlockSize(); + } + } +} diff --git a/pg/src/main/java/org/spongycastle/openpgp/operator/bc/BcPGPDigestCalculatorProvider.java b/pg/src/main/java/org/spongycastle/openpgp/operator/bc/BcPGPDigestCalculatorProvider.java new file mode 100644 index 00000000..50d5fc73 --- /dev/null +++ b/pg/src/main/java/org/spongycastle/openpgp/operator/bc/BcPGPDigestCalculatorProvider.java @@ -0,0 +1,82 @@ +package org.spongycastle.openpgp.operator.bc; + +import java.io.IOException; +import java.io.OutputStream; + +import org.spongycastle.crypto.Digest; +import org.spongycastle.openpgp.PGPException; +import org.spongycastle.openpgp.operator.PGPDigestCalculator; +import org.spongycastle.openpgp.operator.PGPDigestCalculatorProvider; + +public class BcPGPDigestCalculatorProvider + implements PGPDigestCalculatorProvider +{ + public PGPDigestCalculator get(final int algorithm) + throws PGPException + { + final Digest dig = BcImplProvider.createDigest(algorithm); + + final DigestOutputStream stream = new DigestOutputStream(dig); + + return new PGPDigestCalculator() + { + public int getAlgorithm() + { + return algorithm; + } + + public OutputStream getOutputStream() + { + return stream; + } + + public byte[] getDigest() + { + return stream.getDigest(); + } + + public void reset() + { + dig.reset(); + } + }; + } + + private class DigestOutputStream + extends OutputStream + { + private Digest dig; + + DigestOutputStream(Digest dig) + { + this.dig = dig; + } + + public void write(byte[] bytes, int off, int len) + throws IOException + { + dig.update(bytes, off, len); + } + + public void write(byte[] bytes) + throws IOException + { + dig.update(bytes, 0, bytes.length); + } + + public void write(int b) + throws IOException + { + dig.update((byte)b); + } + + byte[] getDigest() + { + byte[] d = new byte[dig.getDigestSize()]; + + dig.doFinal(d, 0); + + return d; + } + } +}
\ No newline at end of file diff --git a/pg/src/main/java/org/spongycastle/openpgp/operator/bc/BcPGPKeyConverter.java b/pg/src/main/java/org/spongycastle/openpgp/operator/bc/BcPGPKeyConverter.java new file mode 100644 index 00000000..40577467 --- /dev/null +++ b/pg/src/main/java/org/spongycastle/openpgp/operator/bc/BcPGPKeyConverter.java @@ -0,0 +1,239 @@ +package org.spongycastle.openpgp.operator.bc; + +import java.util.Date; + +import org.spongycastle.asn1.x9.ECNamedCurveTable; +import org.spongycastle.asn1.x9.X9ECParameters; +import org.spongycastle.bcpg.BCPGKey; +import org.spongycastle.bcpg.DSAPublicBCPGKey; +import org.spongycastle.bcpg.DSASecretBCPGKey; +import org.spongycastle.bcpg.ECDHPublicBCPGKey; +import org.spongycastle.bcpg.ECDSAPublicBCPGKey; +import org.spongycastle.bcpg.ECPublicBCPGKey; +import org.spongycastle.bcpg.ECSecretBCPGKey; +import org.spongycastle.bcpg.ElGamalPublicBCPGKey; +import org.spongycastle.bcpg.ElGamalSecretBCPGKey; +import org.spongycastle.bcpg.HashAlgorithmTags; +import org.spongycastle.bcpg.PublicKeyAlgorithmTags; +import org.spongycastle.bcpg.PublicKeyPacket; +import org.spongycastle.bcpg.RSAPublicBCPGKey; +import org.spongycastle.bcpg.RSASecretBCPGKey; +import org.spongycastle.bcpg.SymmetricKeyAlgorithmTags; +import org.spongycastle.crypto.ec.CustomNamedCurves; +import org.spongycastle.crypto.params.AsymmetricKeyParameter; +import org.spongycastle.crypto.params.DSAParameters; +import org.spongycastle.crypto.params.DSAPrivateKeyParameters; +import org.spongycastle.crypto.params.DSAPublicKeyParameters; +import org.spongycastle.crypto.params.ECNamedDomainParameters; +import org.spongycastle.crypto.params.ECPrivateKeyParameters; +import org.spongycastle.crypto.params.ECPublicKeyParameters; +import org.spongycastle.crypto.params.ElGamalParameters; +import org.spongycastle.crypto.params.ElGamalPrivateKeyParameters; +import org.spongycastle.crypto.params.ElGamalPublicKeyParameters; +import org.spongycastle.crypto.params.RSAKeyParameters; +import org.spongycastle.crypto.params.RSAPrivateCrtKeyParameters; +import org.spongycastle.openpgp.PGPException; +import org.spongycastle.openpgp.PGPPrivateKey; +import org.spongycastle.openpgp.PGPPublicKey; + +public class BcPGPKeyConverter +{ + /** + * Create a PGPPublicKey from the passed in JCA one. + * <p/> + * Note: the time passed in affects the value of the key's keyID, so you probably only want + * to do this once for a JCA key, or make sure you keep track of the time you used. + * + * @param algorithm asymmetric algorithm type representing the public key. + * @param pubKey actual public key to associate. + * @param time date of creation. + * @throws PGPException on key creation problem. + */ + public PGPPublicKey getPGPPublicKey(int algorithm, AsymmetricKeyParameter pubKey, Date time) + throws PGPException + { + BCPGKey bcpgKey; + + if (pubKey instanceof RSAKeyParameters) + { + RSAKeyParameters rK = (RSAKeyParameters)pubKey; + + bcpgKey = new RSAPublicBCPGKey(rK.getModulus(), rK.getExponent()); + } + else if (pubKey instanceof DSAPublicKeyParameters) + { + DSAPublicKeyParameters dK = (DSAPublicKeyParameters)pubKey; + DSAParameters dP = dK.getParameters(); + + bcpgKey = new DSAPublicBCPGKey(dP.getP(), dP.getQ(), dP.getG(), dK.getY()); + } + else if (pubKey instanceof ElGamalPublicKeyParameters) + { + ElGamalPublicKeyParameters eK = (ElGamalPublicKeyParameters)pubKey; + ElGamalParameters eS = eK.getParameters(); + + bcpgKey = new ElGamalPublicBCPGKey(eS.getP(), eS.getG(), eK.getY()); + } + else if (pubKey instanceof ECPublicKeyParameters) + { + ECPublicKeyParameters eK = (ECPublicKeyParameters)pubKey; + + if (algorithm == PGPPublicKey.EC) + { // TODO: KDF parameters setting + bcpgKey = new ECDHPublicBCPGKey(((ECNamedDomainParameters)eK.getParameters()).getName(), eK.getQ(), HashAlgorithmTags.SHA256, SymmetricKeyAlgorithmTags.AES_128); + } + else + { + bcpgKey = new ECDSAPublicBCPGKey(((ECNamedDomainParameters)eK.getParameters()).getName(), eK.getQ()); + } + } + else + { + throw new PGPException("unknown key class"); + } + + return new PGPPublicKey(new PublicKeyPacket(algorithm, time, bcpgKey), new BcKeyFingerprintCalculator()); + } + + public PGPPrivateKey getPGPPrivateKey(PGPPublicKey pubKey, AsymmetricKeyParameter privKey) + throws PGPException + { + BCPGKey privPk; + + switch (pubKey.getAlgorithm()) + { + case PGPPublicKey.RSA_ENCRYPT: + case PGPPublicKey.RSA_SIGN: + case PGPPublicKey.RSA_GENERAL: + RSAPrivateCrtKeyParameters rsK = (RSAPrivateCrtKeyParameters)privKey; + + privPk = new RSASecretBCPGKey(rsK.getExponent(), rsK.getP(), rsK.getQ()); + break; + case PGPPublicKey.DSA: + DSAPrivateKeyParameters dsK = (DSAPrivateKeyParameters)privKey; + + privPk = new DSASecretBCPGKey(dsK.getX()); + break; + case PGPPublicKey.ELGAMAL_ENCRYPT: + case PGPPublicKey.ELGAMAL_GENERAL: + ElGamalPrivateKeyParameters esK = (ElGamalPrivateKeyParameters)privKey; + + privPk = new ElGamalSecretBCPGKey(esK.getX()); + break; + case PGPPublicKey.ECDH: + case PGPPublicKey.ECDSA: + ECPrivateKeyParameters ecK = (ECPrivateKeyParameters)privKey; + + privPk = new ECSecretBCPGKey(ecK.getD()); + break; + default: + throw new PGPException("unknown key class"); + } + return new PGPPrivateKey(pubKey.getKeyID(), pubKey.getPublicKeyPacket(), privPk); + } + + public AsymmetricKeyParameter getPublicKey(PGPPublicKey publicKey) + throws PGPException + { + PublicKeyPacket publicPk = publicKey.getPublicKeyPacket(); + + try + { + switch (publicPk.getAlgorithm()) + { + case PublicKeyAlgorithmTags.RSA_ENCRYPT: + case PublicKeyAlgorithmTags.RSA_GENERAL: + case PublicKeyAlgorithmTags.RSA_SIGN: + RSAPublicBCPGKey rsaK = (RSAPublicBCPGKey)publicPk.getKey(); + + return new RSAKeyParameters(false, rsaK.getModulus(), rsaK.getPublicExponent()); + case PublicKeyAlgorithmTags.DSA: + DSAPublicBCPGKey dsaK = (DSAPublicBCPGKey)publicPk.getKey(); + + return new DSAPublicKeyParameters(dsaK.getY(), new DSAParameters(dsaK.getP(), dsaK.getQ(), dsaK.getG())); + case PublicKeyAlgorithmTags.ELGAMAL_ENCRYPT: + case PublicKeyAlgorithmTags.ELGAMAL_GENERAL: + ElGamalPublicBCPGKey elK = (ElGamalPublicBCPGKey)publicPk.getKey(); + + return new ElGamalPublicKeyParameters(elK.getY(), new ElGamalParameters(elK.getP(), elK.getG())); + case PGPPublicKey.ECDH: + case PGPPublicKey.ECDSA: + ECPublicBCPGKey ecPub = (ECPublicBCPGKey)publicPk.getKey(); + + X9ECParameters x9 = CustomNamedCurves.getByOID(ecPub.getCurveOID()); + if (x9 == null) + { + x9 = ECNamedCurveTable.getByOID(ecPub.getCurveOID()); + } + + return new ECPublicKeyParameters(ecPub.getPoint(), + new ECNamedDomainParameters(ecPub.getCurveOID(), x9.getCurve(), x9.getG(), x9.getN(), x9.getH())); + default: + throw new PGPException("unknown public key algorithm encountered"); + } + } + catch (PGPException e) + { + throw e; + } + catch (Exception e) + { + throw new PGPException("exception constructing public key", e); + } + } + + public AsymmetricKeyParameter getPrivateKey(PGPPrivateKey privKey) + throws PGPException + { + PublicKeyPacket pubPk = privKey.getPublicKeyPacket(); + BCPGKey privPk = privKey.getPrivateKeyDataPacket(); + + try + { + switch (pubPk.getAlgorithm()) + { + case PGPPublicKey.RSA_ENCRYPT: + case PGPPublicKey.RSA_GENERAL: + case PGPPublicKey.RSA_SIGN: + RSAPublicBCPGKey rsaPub = (RSAPublicBCPGKey)pubPk.getKey(); + RSASecretBCPGKey rsaPriv = (RSASecretBCPGKey)privPk; + + return new RSAPrivateCrtKeyParameters(rsaPriv.getModulus(), rsaPub.getPublicExponent(), rsaPriv.getPrivateExponent(), rsaPriv.getPrimeP(), rsaPriv.getPrimeQ(), rsaPriv.getPrimeExponentP(), rsaPriv.getPrimeExponentQ(), rsaPriv.getCrtCoefficient()); + case PGPPublicKey.DSA: + DSAPublicBCPGKey dsaPub = (DSAPublicBCPGKey)pubPk.getKey(); + DSASecretBCPGKey dsaPriv = (DSASecretBCPGKey)privPk; + + return new DSAPrivateKeyParameters(dsaPriv.getX(), new DSAParameters(dsaPub.getP(), dsaPub.getQ(), dsaPub.getG())); + case PGPPublicKey.ELGAMAL_ENCRYPT: + case PGPPublicKey.ELGAMAL_GENERAL: + ElGamalPublicBCPGKey elPub = (ElGamalPublicBCPGKey)pubPk.getKey(); + ElGamalSecretBCPGKey elPriv = (ElGamalSecretBCPGKey)privPk; + + return new ElGamalPrivateKeyParameters(elPriv.getX(), new ElGamalParameters(elPub.getP(), elPub.getG())); + case PGPPublicKey.ECDH: + case PGPPublicKey.ECDSA: + ECPublicBCPGKey ecPub = (ECPublicBCPGKey)pubPk.getKey(); + ECSecretBCPGKey ecPriv = (ECSecretBCPGKey)privPk; + + X9ECParameters x9 = CustomNamedCurves.getByOID(ecPub.getCurveOID()); + if (x9 == null) + { + x9 = ECNamedCurveTable.getByOID(ecPub.getCurveOID()); + } + + return new ECPrivateKeyParameters(ecPriv.getX(), + new ECNamedDomainParameters(ecPub.getCurveOID(), x9.getCurve(), x9.getG(), x9.getN(), x9.getH())); + default: + throw new PGPException("unknown public key algorithm encountered"); + } + } + catch (PGPException e) + { + throw e; + } + catch (Exception e) + { + throw new PGPException("Exception constructing key", e); + } + } +} diff --git a/pg/src/main/java/org/spongycastle/openpgp/operator/bc/BcPGPKeyPair.java b/pg/src/main/java/org/spongycastle/openpgp/operator/bc/BcPGPKeyPair.java new file mode 100644 index 00000000..ed6a0d7e --- /dev/null +++ b/pg/src/main/java/org/spongycastle/openpgp/operator/bc/BcPGPKeyPair.java @@ -0,0 +1,33 @@ +package org.spongycastle.openpgp.operator.bc; + +import java.util.Date; + +import org.spongycastle.crypto.AsymmetricCipherKeyPair; +import org.spongycastle.crypto.params.AsymmetricKeyParameter; +import org.spongycastle.openpgp.PGPException; +import org.spongycastle.openpgp.PGPKeyPair; +import org.spongycastle.openpgp.PGPPrivateKey; +import org.spongycastle.openpgp.PGPPublicKey; + +public class BcPGPKeyPair + extends PGPKeyPair +{ + private static PGPPublicKey getPublicKey(int algorithm, AsymmetricKeyParameter pubKey, Date date) + throws PGPException + { + return new BcPGPKeyConverter().getPGPPublicKey(algorithm, pubKey, date); + } + + private static PGPPrivateKey getPrivateKey(PGPPublicKey pub, AsymmetricKeyParameter privKey) + throws PGPException + { + return new BcPGPKeyConverter().getPGPPrivateKey(pub, privKey); + } + + public BcPGPKeyPair(int algorithm, AsymmetricCipherKeyPair keyPair, Date date) + throws PGPException + { + this.pub = getPublicKey(algorithm, keyPair.getPublic(), date); + this.priv = getPrivateKey(this.pub, keyPair.getPrivate()); + } +} diff --git a/pg/src/main/java/org/spongycastle/openpgp/operator/bc/BcPublicKeyDataDecryptorFactory.java b/pg/src/main/java/org/spongycastle/openpgp/operator/bc/BcPublicKeyDataDecryptorFactory.java new file mode 100644 index 00000000..4c5124bb --- /dev/null +++ b/pg/src/main/java/org/spongycastle/openpgp/operator/bc/BcPublicKeyDataDecryptorFactory.java @@ -0,0 +1,139 @@ +package org.spongycastle.openpgp.operator.bc; + +import org.spongycastle.asn1.nist.NISTNamedCurves; +import org.spongycastle.asn1.x9.X9ECParameters; +import org.spongycastle.bcpg.ECDHPublicBCPGKey; +import org.spongycastle.bcpg.ECSecretBCPGKey; +import org.spongycastle.crypto.AsymmetricBlockCipher; +import org.spongycastle.crypto.BlockCipher; +import org.spongycastle.crypto.BufferedAsymmetricBlockCipher; +import org.spongycastle.crypto.InvalidCipherTextException; +import org.spongycastle.crypto.Wrapper; +import org.spongycastle.crypto.params.AsymmetricKeyParameter; +import org.spongycastle.crypto.params.ElGamalPrivateKeyParameters; +import org.spongycastle.crypto.params.KeyParameter; +import org.spongycastle.math.ec.ECPoint; +import org.spongycastle.openpgp.PGPException; +import org.spongycastle.openpgp.PGPPrivateKey; +import org.spongycastle.openpgp.PGPPublicKey; +import org.spongycastle.openpgp.operator.PGPDataDecryptor; +import org.spongycastle.openpgp.operator.PGPPad; +import org.spongycastle.openpgp.operator.PublicKeyDataDecryptorFactory; +import org.spongycastle.openpgp.operator.RFC6637KDFCalculator; + +/** + * A decryptor factory for handling public key decryption operations. + */ +public class BcPublicKeyDataDecryptorFactory + implements PublicKeyDataDecryptorFactory +{ + private BcPGPKeyConverter keyConverter = new BcPGPKeyConverter(); + private PGPPrivateKey privKey; + + public BcPublicKeyDataDecryptorFactory(PGPPrivateKey privKey) + { + this.privKey = privKey; + } + + public byte[] recoverSessionData(int keyAlgorithm, byte[][] secKeyData) + throws PGPException + { + try + { + if (keyAlgorithm != PGPPublicKey.ECDH) + { + AsymmetricBlockCipher c = BcImplProvider.createPublicKeyCipher(keyAlgorithm); + + AsymmetricKeyParameter key = keyConverter.getPrivateKey(privKey); + + BufferedAsymmetricBlockCipher c1 = new BufferedAsymmetricBlockCipher(c); + + c1.init(false, key); + + if (keyAlgorithm == PGPPublicKey.RSA_ENCRYPT + || keyAlgorithm == PGPPublicKey.RSA_GENERAL) + { + byte[] bi = secKeyData[0]; + + c1.processBytes(bi, 2, bi.length - 2); + } + else + { + BcPGPKeyConverter converter = new BcPGPKeyConverter(); + ElGamalPrivateKeyParameters parms = (ElGamalPrivateKeyParameters)converter.getPrivateKey(privKey); + int size = (parms.getParameters().getP().bitLength() + 7) / 8; + byte[] tmp = new byte[size]; + + byte[] bi = secKeyData[0]; // encoded MPI + if (bi.length - 2 > size) // leading Zero? Shouldn't happen but... + { + c1.processBytes(bi, 3, bi.length - 3); + } + else + { + System.arraycopy(bi, 2, tmp, tmp.length - (bi.length - 2), bi.length - 2); + c1.processBytes(tmp, 0, tmp.length); + } + + bi = secKeyData[1]; // encoded MPI + for (int i = 0; i != tmp.length; i++) + { + tmp[i] = 0; + } + + if (bi.length - 2 > size) // leading Zero? Shouldn't happen but... + { + c1.processBytes(bi, 3, bi.length - 3); + } + else + { + System.arraycopy(bi, 2, tmp, tmp.length - (bi.length - 2), bi.length - 2); + c1.processBytes(tmp, 0, tmp.length); + } + } + + return c1.doFinal(); + } + else + { + ECDHPublicBCPGKey ecKey = (ECDHPublicBCPGKey)privKey.getPublicKeyPacket().getKey(); + X9ECParameters x9Params = NISTNamedCurves.getByOID(ecKey.getCurveOID()); + + byte[] enc = secKeyData[0]; + + int pLen = ((((enc[0] & 0xff) << 8) + (enc[1] & 0xff)) + 7) / 8; + byte[] pEnc = new byte[pLen]; + + System.arraycopy(enc, 2, pEnc, 0, pLen); + + byte[] keyEnc = new byte[enc[pLen + 2]]; + + System.arraycopy(enc, 2 + pLen + 1, keyEnc, 0, keyEnc.length); + + Wrapper c = BcImplProvider.createWrapper(ecKey.getSymmetricKeyAlgorithm()); + + ECPoint S = x9Params.getCurve().decodePoint(pEnc).multiply(((ECSecretBCPGKey)privKey.getPrivateKeyDataPacket()).getX()).normalize(); + + RFC6637KDFCalculator rfc6637KDFCalculator = new RFC6637KDFCalculator(new BcPGPDigestCalculatorProvider().get(ecKey.getHashAlgorithm()), ecKey.getSymmetricKeyAlgorithm()); + KeyParameter key = new KeyParameter(rfc6637KDFCalculator.createKey(ecKey.getCurveOID(), S, new BcKeyFingerprintCalculator().calculateFingerprint(privKey.getPublicKeyPacket()))); + + c.init(false, key); + + return PGPPad.unpadSessionData(c.unwrap(keyEnc, 0, keyEnc.length)); + } + } + catch (InvalidCipherTextException e) + { + throw new PGPException("exception encrypting session info: " + e.getMessage(), e); + } + + } + + public PGPDataDecryptor createDataDecryptor(boolean withIntegrityPacket, int encAlgorithm, byte[] key) + throws PGPException + { + BlockCipher engine = BcImplProvider.createBlockCipher(encAlgorithm); + + return BcUtil.createDataDecryptor(withIntegrityPacket, engine, key); + } +} diff --git a/pg/src/main/java/org/spongycastle/openpgp/operator/bc/BcPublicKeyKeyEncryptionMethodGenerator.java b/pg/src/main/java/org/spongycastle/openpgp/operator/bc/BcPublicKeyKeyEncryptionMethodGenerator.java new file mode 100644 index 00000000..72d501b0 --- /dev/null +++ b/pg/src/main/java/org/spongycastle/openpgp/operator/bc/BcPublicKeyKeyEncryptionMethodGenerator.java @@ -0,0 +1,139 @@ +package org.spongycastle.openpgp.operator.bc; + +import java.io.IOException; +import java.math.BigInteger; +import java.security.SecureRandom; + +import org.spongycastle.asn1.nist.NISTNamedCurves; +import org.spongycastle.asn1.x9.X9ECParameters; +import org.spongycastle.bcpg.ECDHPublicBCPGKey; +import org.spongycastle.bcpg.MPInteger; +import org.spongycastle.crypto.AsymmetricBlockCipher; +import org.spongycastle.crypto.EphemeralKeyPair; +import org.spongycastle.crypto.InvalidCipherTextException; +import org.spongycastle.crypto.KeyEncoder; +import org.spongycastle.crypto.Wrapper; +import org.spongycastle.crypto.generators.ECKeyPairGenerator; +import org.spongycastle.crypto.generators.EphemeralKeyPairGenerator; +import org.spongycastle.crypto.params.AsymmetricKeyParameter; +import org.spongycastle.crypto.params.ECDomainParameters; +import org.spongycastle.crypto.params.ECKeyGenerationParameters; +import org.spongycastle.crypto.params.ECPrivateKeyParameters; +import org.spongycastle.crypto.params.ECPublicKeyParameters; +import org.spongycastle.crypto.params.KeyParameter; +import org.spongycastle.crypto.params.ParametersWithRandom; +import org.spongycastle.math.ec.ECPoint; +import org.spongycastle.openpgp.PGPException; +import org.spongycastle.openpgp.PGPPublicKey; +import org.spongycastle.openpgp.operator.PGPPad; +import org.spongycastle.openpgp.operator.PublicKeyKeyEncryptionMethodGenerator; +import org.spongycastle.openpgp.operator.RFC6637KDFCalculator; + +/** + * A method generator for supporting public key based encryption operations. + */ +public class BcPublicKeyKeyEncryptionMethodGenerator + extends PublicKeyKeyEncryptionMethodGenerator +{ + private SecureRandom random; + private BcPGPKeyConverter keyConverter = new BcPGPKeyConverter(); + + /** + * Create a public key encryption method generator with the method to be based on the passed in key. + * + * @param key the public key to use for encryption. + */ + public BcPublicKeyKeyEncryptionMethodGenerator(PGPPublicKey key) + { + super(key); + } + + /** + * Provide a user defined source of randomness. + * + * @param random the secure random to be used. + * @return the current generator. + */ + public BcPublicKeyKeyEncryptionMethodGenerator setSecureRandom(SecureRandom random) + { + this.random = random; + + return this; + } + + protected byte[] encryptSessionInfo(PGPPublicKey pubKey, byte[] sessionInfo) + throws PGPException + { + try + { + if (pubKey.getAlgorithm() != PGPPublicKey.ECDH) + { + AsymmetricBlockCipher c = BcImplProvider.createPublicKeyCipher(pubKey.getAlgorithm()); + + AsymmetricKeyParameter key = keyConverter.getPublicKey(pubKey); + + if (random == null) + { + random = new SecureRandom(); + } + + c.init(true, new ParametersWithRandom(key, random)); + + return c.processBlock(sessionInfo, 0, sessionInfo.length); + } + else + { + ECDHPublicBCPGKey ecKey = (ECDHPublicBCPGKey)pubKey.getPublicKeyPacket().getKey(); + X9ECParameters x9Params = NISTNamedCurves.getByOID(ecKey.getCurveOID()); + ECDomainParameters ecParams = new ECDomainParameters(x9Params.getCurve(), x9Params.getG(), x9Params.getN()); + + // Generate the ephemeral key pair + ECKeyPairGenerator gen = new ECKeyPairGenerator(); + gen.init(new ECKeyGenerationParameters(ecParams, random)); + + EphemeralKeyPairGenerator kGen = new EphemeralKeyPairGenerator(gen, new KeyEncoder() + { + public byte[] getEncoded(AsymmetricKeyParameter keyParameter) + { + return ((ECPublicKeyParameters)keyParameter).getQ().getEncoded(false); + } + }); + + EphemeralKeyPair ephKp = kGen.generate(); + + ECPrivateKeyParameters ephPriv = (ECPrivateKeyParameters)ephKp.getKeyPair().getPrivate(); + + ECPoint S = ecKey.getPoint().multiply(ephPriv.getD()).normalize(); + + RFC6637KDFCalculator rfc6637KDFCalculator = new RFC6637KDFCalculator(new BcPGPDigestCalculatorProvider().get(ecKey.getHashAlgorithm()), ecKey.getSymmetricKeyAlgorithm()); + + KeyParameter key = new KeyParameter(rfc6637KDFCalculator.createKey(ecKey.getCurveOID(), S, pubKey.getFingerprint())); + + Wrapper c = BcImplProvider.createWrapper(ecKey.getSymmetricKeyAlgorithm()); + + c.init(true, new ParametersWithRandom(key, random)); + + byte[] paddedSessionData = PGPPad.padSessionData(sessionInfo); + + byte[] C = c.wrap(paddedSessionData, 0, paddedSessionData.length); + byte[] VB = new MPInteger(new BigInteger(1, ephKp.getEncodedPublicKey())).getEncoded(); + + byte[] rv = new byte[VB.length + 1 + C.length]; + + System.arraycopy(VB, 0, rv, 0, VB.length); + rv[VB.length] = (byte)C.length; + System.arraycopy(C, 0, rv, VB.length + 1, C.length); + + return rv; + } + } + catch (InvalidCipherTextException e) + { + throw new PGPException("exception encrypting session info: " + e.getMessage(), e); + } + catch (IOException e) + { + throw new PGPException("exception encrypting session info: " + e.getMessage(), e); + } + } +} diff --git a/pg/src/main/java/org/spongycastle/openpgp/operator/bc/BcUtil.java b/pg/src/main/java/org/spongycastle/openpgp/operator/bc/BcUtil.java new file mode 100644 index 00000000..c3be8c83 --- /dev/null +++ b/pg/src/main/java/org/spongycastle/openpgp/operator/bc/BcUtil.java @@ -0,0 +1,75 @@ +package org.spongycastle.openpgp.operator.bc; + +import java.io.InputStream; + +import org.spongycastle.crypto.BlockCipher; +import org.spongycastle.crypto.BufferedBlockCipher; +import org.spongycastle.crypto.io.CipherInputStream; +import org.spongycastle.crypto.modes.CFBBlockCipher; +import org.spongycastle.crypto.modes.OpenPGPCFBBlockCipher; +import org.spongycastle.crypto.params.KeyParameter; +import org.spongycastle.crypto.params.ParametersWithIV; +import org.spongycastle.openpgp.operator.PGPDataDecryptor; +import org.spongycastle.openpgp.operator.PGPDigestCalculator; + +class BcUtil +{ + static BufferedBlockCipher createStreamCipher(boolean forEncryption, BlockCipher engine, boolean withIntegrityPacket, byte[] key) + { + BufferedBlockCipher c; + + if (withIntegrityPacket) + { + c = new BufferedBlockCipher(new CFBBlockCipher(engine, engine.getBlockSize() * 8)); + } + else + { + c = new BufferedBlockCipher(new OpenPGPCFBBlockCipher(engine)); + } + + KeyParameter keyParameter = new KeyParameter(key); + + if (withIntegrityPacket) + { + c.init(forEncryption, new ParametersWithIV(keyParameter, new byte[engine.getBlockSize()])); + } + else + { + c.init(forEncryption, keyParameter); + } + + return c; + } + + public static PGPDataDecryptor createDataDecryptor(boolean withIntegrityPacket, BlockCipher engine, byte[] key) + { + final BufferedBlockCipher c = createStreamCipher(false, engine, withIntegrityPacket, key); + + return new PGPDataDecryptor() + { + public InputStream getInputStream(InputStream in) + { + return new CipherInputStream(in, c); + } + + public int getBlockSize() + { + return c.getBlockSize(); + } + + public PGPDigestCalculator getIntegrityCalculator() + { + return new SHA1PGPDigestCalculator(); + } + }; + } + + public static BufferedBlockCipher createSymmetricKeyWrapper(boolean forEncryption, BlockCipher engine, byte[] key, byte[] iv) + { + BufferedBlockCipher c = new BufferedBlockCipher(new CFBBlockCipher(engine, engine.getBlockSize() * 8)); + + c.init(forEncryption, new ParametersWithIV(new KeyParameter(key), iv)); + + return c; + } +} diff --git a/pg/src/main/java/org/spongycastle/openpgp/operator/bc/SHA1PGPDigestCalculator.java b/pg/src/main/java/org/spongycastle/openpgp/operator/bc/SHA1PGPDigestCalculator.java new file mode 100644 index 00000000..15572a39 --- /dev/null +++ b/pg/src/main/java/org/spongycastle/openpgp/operator/bc/SHA1PGPDigestCalculator.java @@ -0,0 +1,68 @@ +package org.spongycastle.openpgp.operator.bc; + +import java.io.IOException; +import java.io.OutputStream; + +import org.spongycastle.bcpg.HashAlgorithmTags; +import org.spongycastle.crypto.Digest; +import org.spongycastle.crypto.digests.SHA1Digest; +import org.spongycastle.openpgp.operator.PGPDigestCalculator; + +class SHA1PGPDigestCalculator + implements PGPDigestCalculator +{ + private Digest digest = new SHA1Digest(); + + public int getAlgorithm() + { + return HashAlgorithmTags.SHA1; + } + + public OutputStream getOutputStream() + { + return new DigestOutputStream(digest); + } + + public byte[] getDigest() + { + byte[] d = new byte[digest.getDigestSize()]; + + digest.doFinal(d, 0); + + return d; + } + + public void reset() + { + digest.reset(); + } + + private class DigestOutputStream + extends OutputStream + { + private Digest dig; + + DigestOutputStream(Digest dig) + { + this.dig = dig; + } + + public void write(byte[] bytes, int off, int len) + throws IOException + { + dig.update(bytes, off, len); + } + + public void write(byte[] bytes) + throws IOException + { + dig.update(bytes, 0, bytes.length); + } + + public void write(int b) + throws IOException + { + dig.update((byte)b); + } + } +} diff --git a/pg/src/main/java/org/spongycastle/openpgp/operator/bc/SignerOutputStream.java b/pg/src/main/java/org/spongycastle/openpgp/operator/bc/SignerOutputStream.java new file mode 100644 index 00000000..cdc2d7e3 --- /dev/null +++ b/pg/src/main/java/org/spongycastle/openpgp/operator/bc/SignerOutputStream.java @@ -0,0 +1,35 @@ +package org.spongycastle.openpgp.operator.bc; + +import java.io.IOException; +import java.io.OutputStream; + +import org.spongycastle.crypto.Signer; + +class SignerOutputStream + extends OutputStream +{ + private Signer sig; + + SignerOutputStream(Signer sig) + { + this.sig = sig; + } + + public void write(byte[] bytes, int off, int len) + throws IOException + { + sig.update(bytes, off, len); + } + + public void write(byte[] bytes) + throws IOException + { + sig.update(bytes, 0, bytes.length); + } + + public void write(int b) + throws IOException + { + sig.update((byte)b); + } +} diff --git a/pg/src/main/java/org/spongycastle/openpgp/operator/jcajce/JcaKeyFingerprintCalculator.java b/pg/src/main/java/org/spongycastle/openpgp/operator/jcajce/JcaKeyFingerprintCalculator.java new file mode 100644 index 00000000..e72955f2 --- /dev/null +++ b/pg/src/main/java/org/spongycastle/openpgp/operator/jcajce/JcaKeyFingerprintCalculator.java @@ -0,0 +1,74 @@ +package org.spongycastle.openpgp.operator.jcajce; + +import java.io.IOException; +import java.security.MessageDigest; +import java.security.NoSuchAlgorithmException; + +import org.spongycastle.bcpg.BCPGKey; +import org.spongycastle.bcpg.MPInteger; +import org.spongycastle.bcpg.PublicKeyPacket; +import org.spongycastle.bcpg.RSAPublicBCPGKey; +import org.spongycastle.openpgp.PGPException; +import org.spongycastle.openpgp.operator.KeyFingerPrintCalculator; + +public class JcaKeyFingerprintCalculator + implements KeyFingerPrintCalculator +{ + + // FIXME: Convert this to builder style so we can set provider? + public byte[] calculateFingerprint(PublicKeyPacket publicPk) + throws PGPException + { + BCPGKey key = publicPk.getKey(); + + if (publicPk.getVersion() <= 3) + { + RSAPublicBCPGKey rK = (RSAPublicBCPGKey)key; + + try + { + MessageDigest digest = MessageDigest.getInstance("MD5"); + + byte[] bytes = new MPInteger(rK.getModulus()).getEncoded(); + digest.update(bytes, 2, bytes.length - 2); + + bytes = new MPInteger(rK.getPublicExponent()).getEncoded(); + digest.update(bytes, 2, bytes.length - 2); + + return digest.digest(); + } + catch (NoSuchAlgorithmException e) + { + throw new PGPException("can't find MD5", e); + } + catch (IOException e) + { + throw new PGPException("can't encode key components: " + e.getMessage(), e); + } + } + else + { + try + { + byte[] kBytes = publicPk.getEncodedContents(); + + MessageDigest digest = MessageDigest.getInstance("SHA1"); + + digest.update((byte)0x99); + digest.update((byte)(kBytes.length >> 8)); + digest.update((byte)kBytes.length); + digest.update(kBytes); + + return digest.digest(); + } + catch (NoSuchAlgorithmException e) + { + throw new PGPException("can't find SHA1", e); + } + catch (IOException e) + { + throw new PGPException("can't encode key components: " + e.getMessage(), e); + } + } + } +} diff --git a/pg/src/main/java/org/spongycastle/openpgp/operator/jcajce/JcaPGPContentSignerBuilder.java b/pg/src/main/java/org/spongycastle/openpgp/operator/jcajce/JcaPGPContentSignerBuilder.java new file mode 100644 index 00000000..5cb6f007 --- /dev/null +++ b/pg/src/main/java/org/spongycastle/openpgp/operator/jcajce/JcaPGPContentSignerBuilder.java @@ -0,0 +1,156 @@ +package org.spongycastle.openpgp.operator.jcajce; + +import java.io.OutputStream; +import java.security.InvalidKeyException; +import java.security.PrivateKey; +import java.security.Provider; +import java.security.SecureRandom; +import java.security.Signature; +import java.security.SignatureException; + +import org.spongycastle.jcajce.util.DefaultJcaJceHelper; +import org.spongycastle.jcajce.util.NamedJcaJceHelper; +import org.spongycastle.jcajce.util.ProviderJcaJceHelper; +import org.spongycastle.openpgp.PGPException; +import org.spongycastle.openpgp.PGPPrivateKey; +import org.spongycastle.openpgp.operator.PGPContentSigner; +import org.spongycastle.openpgp.operator.PGPContentSignerBuilder; +import org.spongycastle.openpgp.operator.PGPDigestCalculator; +import org.spongycastle.util.io.TeeOutputStream; + +public class JcaPGPContentSignerBuilder + implements PGPContentSignerBuilder +{ + private OperatorHelper helper = new OperatorHelper(new DefaultJcaJceHelper()); + private JcaPGPDigestCalculatorProviderBuilder digestCalculatorProviderBuilder = new JcaPGPDigestCalculatorProviderBuilder(); + private JcaPGPKeyConverter keyConverter = new JcaPGPKeyConverter(); + private int hashAlgorithm; + private SecureRandom random; + private int keyAlgorithm; + + public JcaPGPContentSignerBuilder(int keyAlgorithm, int hashAlgorithm) + { + this.keyAlgorithm = keyAlgorithm; + this.hashAlgorithm = hashAlgorithm; + } + + public JcaPGPContentSignerBuilder setSecureRandom(SecureRandom random) + { + this.random = random; + + return this; + } + + public JcaPGPContentSignerBuilder setProvider(Provider provider) + { + this.helper = new OperatorHelper(new ProviderJcaJceHelper(provider)); + keyConverter.setProvider(provider); + digestCalculatorProviderBuilder.setProvider(provider); + + return this; + } + + public JcaPGPContentSignerBuilder setProvider(String providerName) + { + this.helper = new OperatorHelper(new NamedJcaJceHelper(providerName)); + keyConverter.setProvider(providerName); + digestCalculatorProviderBuilder.setProvider(providerName); + + return this; + } + + public JcaPGPContentSignerBuilder setDigestProvider(Provider provider) + { + digestCalculatorProviderBuilder.setProvider(provider); + + return this; + } + + public JcaPGPContentSignerBuilder setDigestProvider(String providerName) + { + digestCalculatorProviderBuilder.setProvider(providerName); + + return this; + } + + public PGPContentSigner build(final int signatureType, PGPPrivateKey privateKey) + throws PGPException + { + if (privateKey instanceof JcaPGPPrivateKey) + { + return build(signatureType, privateKey.getKeyID(), ((JcaPGPPrivateKey)privateKey).getPrivateKey()); + } + else + { + return build(signatureType, privateKey.getKeyID(), keyConverter.getPrivateKey(privateKey)); + } + } + + public PGPContentSigner build(final int signatureType, final long keyID, final PrivateKey privateKey) + throws PGPException + { + final PGPDigestCalculator digestCalculator = digestCalculatorProviderBuilder.build().get(hashAlgorithm); + final Signature signature = helper.createSignature(keyAlgorithm, hashAlgorithm); + + try + { + if (random != null) + { + signature.initSign(privateKey, random); + } + else + { + signature.initSign(privateKey); + } + } + catch (InvalidKeyException e) + { + throw new PGPException("invalid key.", e); + } + + return new PGPContentSigner() + { + public int getType() + { + return signatureType; + } + + public int getHashAlgorithm() + { + return hashAlgorithm; + } + + public int getKeyAlgorithm() + { + return keyAlgorithm; + } + + public long getKeyID() + { + return keyID; + } + + public OutputStream getOutputStream() + { + return new TeeOutputStream(new SignatureOutputStream(signature), digestCalculator.getOutputStream()); + } + + public byte[] getSignature() + { + try + { + return signature.sign(); + } + catch (SignatureException e) + { // TODO: need a specific runtime exception for PGP operators. + throw new IllegalStateException("unable to create signature"); + } + } + + public byte[] getDigest() + { + return digestCalculator.getDigest(); + } + }; + } +} diff --git a/pg/src/main/java/org/spongycastle/openpgp/operator/jcajce/JcaPGPContentVerifierBuilderProvider.java b/pg/src/main/java/org/spongycastle/openpgp/operator/jcajce/JcaPGPContentVerifierBuilderProvider.java new file mode 100644 index 00000000..99973776 --- /dev/null +++ b/pg/src/main/java/org/spongycastle/openpgp/operator/jcajce/JcaPGPContentVerifierBuilderProvider.java @@ -0,0 +1,113 @@ +package org.spongycastle.openpgp.operator.jcajce; + +import java.io.OutputStream; +import java.security.InvalidKeyException; +import java.security.Provider; +import java.security.Signature; +import java.security.SignatureException; + +import org.spongycastle.jcajce.util.DefaultJcaJceHelper; +import org.spongycastle.jcajce.util.NamedJcaJceHelper; +import org.spongycastle.jcajce.util.ProviderJcaJceHelper; +import org.spongycastle.openpgp.PGPException; +import org.spongycastle.openpgp.PGPPublicKey; +import org.spongycastle.openpgp.PGPRuntimeOperationException; +import org.spongycastle.openpgp.operator.PGPContentVerifier; +import org.spongycastle.openpgp.operator.PGPContentVerifierBuilder; +import org.spongycastle.openpgp.operator.PGPContentVerifierBuilderProvider; + +public class JcaPGPContentVerifierBuilderProvider + implements PGPContentVerifierBuilderProvider +{ + private OperatorHelper helper = new OperatorHelper(new DefaultJcaJceHelper()); + private JcaPGPKeyConverter keyConverter = new JcaPGPKeyConverter(); + + public JcaPGPContentVerifierBuilderProvider() + { + } + + public JcaPGPContentVerifierBuilderProvider setProvider(Provider provider) + { + this.helper = new OperatorHelper(new ProviderJcaJceHelper(provider)); + keyConverter.setProvider(provider); + + return this; + } + + public JcaPGPContentVerifierBuilderProvider setProvider(String providerName) + { + this.helper = new OperatorHelper(new NamedJcaJceHelper(providerName)); + keyConverter.setProvider(providerName); + + return this; + } + + public PGPContentVerifierBuilder get(int keyAlgorithm, int hashAlgorithm) + throws PGPException + { + return new JcaPGPContentVerifierBuilder(keyAlgorithm, hashAlgorithm); + } + + private class JcaPGPContentVerifierBuilder + implements PGPContentVerifierBuilder + { + private int hashAlgorithm; + private int keyAlgorithm; + + public JcaPGPContentVerifierBuilder(int keyAlgorithm, int hashAlgorithm) + { + this.keyAlgorithm = keyAlgorithm; + this.hashAlgorithm = hashAlgorithm; + } + + public PGPContentVerifier build(final PGPPublicKey publicKey) + throws PGPException + { + final Signature signature = helper.createSignature(keyAlgorithm, hashAlgorithm); + + try + { + signature.initVerify(keyConverter.getPublicKey(publicKey)); + } + catch (InvalidKeyException e) + { + throw new PGPException("invalid key.", e); + } + + return new PGPContentVerifier() + { + public int getHashAlgorithm() + { + return hashAlgorithm; + } + + public int getKeyAlgorithm() + { + return keyAlgorithm; + } + + public long getKeyID() + { + return publicKey.getKeyID(); + } + + public boolean verify(byte[] expected) + { + try + { + return signature.verify(expected); + } + catch (SignatureException e) + { + throw new PGPRuntimeOperationException("unable to verify signature: " + e.getMessage(), e); + } + } + + public OutputStream getOutputStream() + { + return new SignatureOutputStream(signature); + } + }; + } + } +} diff --git a/pg/src/main/java/org/spongycastle/openpgp/operator/jcajce/JcaPGPDigestCalculatorProviderBuilder.java b/pg/src/main/java/org/spongycastle/openpgp/operator/jcajce/JcaPGPDigestCalculatorProviderBuilder.java new file mode 100644 index 00000000..753e289c --- /dev/null +++ b/pg/src/main/java/org/spongycastle/openpgp/operator/jcajce/JcaPGPDigestCalculatorProviderBuilder.java @@ -0,0 +1,149 @@ +package org.spongycastle.openpgp.operator.jcajce; + +import java.io.IOException; +import java.io.OutputStream; +import java.security.GeneralSecurityException; +import java.security.MessageDigest; +import java.security.Provider; + +import org.spongycastle.jcajce.util.DefaultJcaJceHelper; +import org.spongycastle.jcajce.util.NamedJcaJceHelper; +import org.spongycastle.jcajce.util.ProviderJcaJceHelper; +import org.spongycastle.openpgp.PGPException; +import org.spongycastle.openpgp.operator.PGPDigestCalculator; +import org.spongycastle.openpgp.operator.PGPDigestCalculatorProvider; + +/** + * A builder for {@link PGPDigestCalculatorProvider} instances that obtain cryptographic primitives + * using the JCA API. + * <p/> + * By default digest calculator providers obtained from this builder will use the default JCA + * algorithm lookup mechanisms (i.e. specifying no provider), but a specific provider can be + * specified prior to building. + */ +public class JcaPGPDigestCalculatorProviderBuilder +{ + private OperatorHelper helper = new OperatorHelper(new DefaultJcaJceHelper()); + + /** + * Default constructor. + */ + public JcaPGPDigestCalculatorProviderBuilder() + { + } + + /** + * Sets the provider to use to obtain cryptographic primitives. + * + * @param provider the JCA provider to use. + * @return the current builder. + */ + public JcaPGPDigestCalculatorProviderBuilder setProvider(Provider provider) + { + this.helper = new OperatorHelper(new ProviderJcaJceHelper(provider)); + + return this; + } + + /** + * Sets the provider to use to obtain cryptographic primitives. + * + * @param providerName the name of the JCA provider to use. + * @return the current builder. + */ + public JcaPGPDigestCalculatorProviderBuilder setProvider(String providerName) + { + this.helper = new OperatorHelper(new NamedJcaJceHelper(providerName)); + + return this; + } + + /** + * Constructs a new PGPDigestCalculatorProvider + * + * @return a PGPDigestCalculatorProvider that will use the JCA algorithm lookup strategy + * configured on this builder. + * @throws PGPException if an error occurs constructing the digest calculator provider. + */ + public PGPDigestCalculatorProvider build() + throws PGPException + { + return new PGPDigestCalculatorProvider() + { + public PGPDigestCalculator get(final int algorithm) + throws PGPException + { + final DigestOutputStream stream; + final MessageDigest dig; + + try + { + dig = helper.createDigest(algorithm); + + stream = new DigestOutputStream(dig); + } + catch (GeneralSecurityException e) + { + throw new PGPException("exception on setup: " + e, e); + } + + return new PGPDigestCalculator() + { + public int getAlgorithm() + { + return algorithm; + } + + public OutputStream getOutputStream() + { + return stream; + } + + public byte[] getDigest() + { + return stream.getDigest(); + } + + public void reset() + { + dig.reset(); + } + }; + } + }; + } + + private class DigestOutputStream + extends OutputStream + { + private MessageDigest dig; + + DigestOutputStream(MessageDigest dig) + { + this.dig = dig; + } + + public void write(byte[] bytes, int off, int len) + throws IOException + { + dig.update(bytes, off, len); + } + + public void write(byte[] bytes) + throws IOException + { + dig.update(bytes); + } + + public void write(int b) + throws IOException + { + dig.update((byte)b); + } + + byte[] getDigest() + { + return dig.digest(); + } + } +} diff --git a/pg/src/main/java/org/spongycastle/openpgp/operator/jcajce/JcaPGPKeyConverter.java b/pg/src/main/java/org/spongycastle/openpgp/operator/jcajce/JcaPGPKeyConverter.java new file mode 100644 index 00000000..2f82fdfa --- /dev/null +++ b/pg/src/main/java/org/spongycastle/openpgp/operator/jcajce/JcaPGPKeyConverter.java @@ -0,0 +1,377 @@ +package org.spongycastle.openpgp.operator.jcajce; + +import java.security.KeyFactory; +import java.security.PrivateKey; +import java.security.Provider; +import java.security.PublicKey; +import java.security.interfaces.DSAParams; +import java.security.interfaces.DSAPrivateKey; +import java.security.interfaces.DSAPublicKey; +import java.security.interfaces.ECPrivateKey; +import java.security.interfaces.ECPublicKey; +import java.security.interfaces.RSAPrivateCrtKey; +import java.security.interfaces.RSAPublicKey; +import java.security.spec.DSAPrivateKeySpec; +import java.security.spec.DSAPublicKeySpec; +import java.security.spec.ECParameterSpec; +import java.security.spec.ECPrivateKeySpec; +import java.security.spec.ECPublicKeySpec; +import java.security.spec.RSAPrivateCrtKeySpec; +import java.security.spec.RSAPublicKeySpec; +import java.util.Date; + +import org.spongycastle.asn1.ASN1ObjectIdentifier; +import org.spongycastle.asn1.ASN1OctetString; +import org.spongycastle.asn1.DEROctetString; +import org.spongycastle.asn1.nist.NISTNamedCurves; +import org.spongycastle.asn1.x509.SubjectPublicKeyInfo; +import org.spongycastle.asn1.x9.ECNamedCurveTable; +import org.spongycastle.asn1.x9.X9ECParameters; +import org.spongycastle.asn1.x9.X9ECPoint; +import org.spongycastle.bcpg.BCPGKey; +import org.spongycastle.bcpg.DSAPublicBCPGKey; +import org.spongycastle.bcpg.DSASecretBCPGKey; +import org.spongycastle.bcpg.ECDHPublicBCPGKey; +import org.spongycastle.bcpg.ECDSAPublicBCPGKey; +import org.spongycastle.bcpg.ECSecretBCPGKey; +import org.spongycastle.bcpg.ElGamalPublicBCPGKey; +import org.spongycastle.bcpg.ElGamalSecretBCPGKey; +import org.spongycastle.bcpg.HashAlgorithmTags; +import org.spongycastle.bcpg.PublicKeyAlgorithmTags; +import org.spongycastle.bcpg.PublicKeyPacket; +import org.spongycastle.bcpg.RSAPublicBCPGKey; +import org.spongycastle.bcpg.RSASecretBCPGKey; +import org.spongycastle.bcpg.SymmetricKeyAlgorithmTags; +import org.spongycastle.crypto.ec.CustomNamedCurves; +import org.spongycastle.jcajce.util.DefaultJcaJceHelper; +import org.spongycastle.jcajce.util.NamedJcaJceHelper; +import org.spongycastle.jcajce.util.ProviderJcaJceHelper; +import org.spongycastle.jce.interfaces.ElGamalPrivateKey; +import org.spongycastle.jce.interfaces.ElGamalPublicKey; +import org.spongycastle.jce.spec.ECNamedCurveSpec; +import org.spongycastle.jce.spec.ElGamalParameterSpec; +import org.spongycastle.jce.spec.ElGamalPrivateKeySpec; +import org.spongycastle.jce.spec.ElGamalPublicKeySpec; +import org.spongycastle.openpgp.PGPAlgorithmParameters; +import org.spongycastle.openpgp.PGPException; +import org.spongycastle.openpgp.PGPKdfParameters; +import org.spongycastle.openpgp.PGPPrivateKey; +import org.spongycastle.openpgp.PGPPublicKey; +import org.spongycastle.openpgp.operator.KeyFingerPrintCalculator; + +public class JcaPGPKeyConverter +{ + private OperatorHelper helper = new OperatorHelper(new DefaultJcaJceHelper()); + private KeyFingerPrintCalculator fingerPrintCalculator = new JcaKeyFingerprintCalculator(); + + public JcaPGPKeyConverter setProvider(Provider provider) + { + this.helper = new OperatorHelper(new ProviderJcaJceHelper(provider)); + + return this; + } + + public JcaPGPKeyConverter setProvider(String providerName) + { + this.helper = new OperatorHelper(new NamedJcaJceHelper(providerName)); + + return this; + } + + public PublicKey getPublicKey(PGPPublicKey publicKey) + throws PGPException + { + KeyFactory fact; + + PublicKeyPacket publicPk = publicKey.getPublicKeyPacket(); + + try + { + switch (publicPk.getAlgorithm()) + { + case PublicKeyAlgorithmTags.RSA_ENCRYPT: + case PublicKeyAlgorithmTags.RSA_GENERAL: + case PublicKeyAlgorithmTags.RSA_SIGN: + RSAPublicBCPGKey rsaK = (RSAPublicBCPGKey)publicPk.getKey(); + RSAPublicKeySpec rsaSpec = new RSAPublicKeySpec(rsaK.getModulus(), rsaK.getPublicExponent()); + + fact = helper.createKeyFactory("RSA"); + + return fact.generatePublic(rsaSpec); + case PublicKeyAlgorithmTags.DSA: + DSAPublicBCPGKey dsaK = (DSAPublicBCPGKey)publicPk.getKey(); + DSAPublicKeySpec dsaSpec = new DSAPublicKeySpec(dsaK.getY(), dsaK.getP(), dsaK.getQ(), dsaK.getG()); + + fact = helper.createKeyFactory("DSA"); + + return fact.generatePublic(dsaSpec); + case PublicKeyAlgorithmTags.ELGAMAL_ENCRYPT: + case PublicKeyAlgorithmTags.ELGAMAL_GENERAL: + ElGamalPublicBCPGKey elK = (ElGamalPublicBCPGKey)publicPk.getKey(); + ElGamalPublicKeySpec elSpec = new ElGamalPublicKeySpec(elK.getY(), new ElGamalParameterSpec(elK.getP(), elK.getG())); + + fact = helper.createKeyFactory("ElGamal"); + + return fact.generatePublic(elSpec); + case PublicKeyAlgorithmTags.EC: + ECDHPublicBCPGKey ecdhK = (ECDHPublicBCPGKey)publicPk.getKey(); + ECPublicKeySpec ecDhSpec = new ECPublicKeySpec( + new java.security.spec.ECPoint(ecdhK.getPoint().getAffineXCoord().toBigInteger(), ecdhK.getPoint().getAffineYCoord().toBigInteger()), + getX9Parameters(ecdhK.getCurveOID())); + fact = helper.createKeyFactory("ECDH"); + + return fact.generatePublic(ecDhSpec); + case PublicKeyAlgorithmTags.ECDSA: + ECDSAPublicBCPGKey ecdsaK = (ECDSAPublicBCPGKey)publicPk.getKey(); + ECPublicKeySpec ecDsaSpec = new ECPublicKeySpec( + new java.security.spec.ECPoint(ecdsaK.getPoint().getAffineXCoord().toBigInteger(), ecdsaK.getPoint().getAffineYCoord().toBigInteger()), + getX9Parameters(ecdsaK.getCurveOID())); + fact = helper.createKeyFactory("ECDSA"); + + return fact.generatePublic(ecDsaSpec); + default: + throw new PGPException("unknown public key algorithm encountered"); + } + } + catch (PGPException e) + { + throw e; + } + catch (Exception e) + { + throw new PGPException("exception constructing public key", e); + } + } + + /** + * Create a PGPPublicKey from the passed in JCA one. + * <p/> + * Note: the time passed in affects the value of the key's keyID, so you probably only want + * to do this once for a JCA key, or make sure you keep track of the time you used. + * + * @param algorithm asymmetric algorithm type representing the public key. + * @param algorithmParameters additional parameters to be stored against the public key. + * @param pubKey actual public key to associate. + * @param time date of creation. + * @throws PGPException on key creation problem. + */ + public PGPPublicKey getPGPPublicKey(int algorithm, PGPAlgorithmParameters algorithmParameters, PublicKey pubKey, Date time) + throws PGPException + { + BCPGKey bcpgKey; + + if (pubKey instanceof RSAPublicKey) + { + RSAPublicKey rK = (RSAPublicKey)pubKey; + + bcpgKey = new RSAPublicBCPGKey(rK.getModulus(), rK.getPublicExponent()); + } + else if (pubKey instanceof DSAPublicKey) + { + DSAPublicKey dK = (DSAPublicKey)pubKey; + DSAParams dP = dK.getParams(); + + bcpgKey = new DSAPublicBCPGKey(dP.getP(), dP.getQ(), dP.getG(), dK.getY()); + } + else if (pubKey instanceof ElGamalPublicKey) + { + ElGamalPublicKey eK = (ElGamalPublicKey)pubKey; + ElGamalParameterSpec eS = eK.getParameters(); + + bcpgKey = new ElGamalPublicBCPGKey(eS.getP(), eS.getG(), eK.getY()); + } + else if (pubKey instanceof ECPublicKey) + { + SubjectPublicKeyInfo keyInfo = SubjectPublicKeyInfo.getInstance(pubKey.getEncoded()); + + // TODO: should probably match curve by comparison as well + ASN1ObjectIdentifier curveOid = ASN1ObjectIdentifier.getInstance(keyInfo.getAlgorithm().getParameters()); + + X9ECParameters params = NISTNamedCurves.getByOID(curveOid); + + ASN1OctetString key = new DEROctetString(keyInfo.getPublicKeyData().getBytes()); + X9ECPoint derQ = new X9ECPoint(params.getCurve(), key); + + if (algorithm == PGPPublicKey.EC) + { + PGPKdfParameters kdfParams = (PGPKdfParameters)algorithmParameters; + if (kdfParams == null) + { + // We default to these as they are specified as mandatory in RFC 6631. + kdfParams = new PGPKdfParameters(HashAlgorithmTags.SHA256, SymmetricKeyAlgorithmTags.AES_128); + } + bcpgKey = new ECDHPublicBCPGKey(curveOid, derQ.getPoint(), kdfParams.getHashAlgorithm(), kdfParams.getSymmetricWrapAlgorithm()); + } + else + { + bcpgKey = new ECDSAPublicBCPGKey(curveOid, derQ.getPoint()); + } + } + else + { + throw new PGPException("unknown key class"); + } + + return new PGPPublicKey(new PublicKeyPacket(algorithm, time, bcpgKey), fingerPrintCalculator); + } + + /** + * Create a PGPPublicKey from the passed in JCA one. + * <p/> + * Note: the time passed in affects the value of the key's keyID, so you probably only want + * to do this once for a JCA key, or make sure you keep track of the time you used. + * + * @param algorithm asymmetric algorithm type representing the public key. + * @param pubKey actual public key to associate. + * @param time date of creation. + * @throws PGPException on key creation problem. + */ + public PGPPublicKey getPGPPublicKey(int algorithm, PublicKey pubKey, Date time) + throws PGPException + { + return getPGPPublicKey(algorithm, null, pubKey, time); + } + + public PrivateKey getPrivateKey(PGPPrivateKey privKey) + throws PGPException + { + if (privKey instanceof JcaPGPPrivateKey) + { + return ((JcaPGPPrivateKey)privKey).getPrivateKey(); + } + + PublicKeyPacket pubPk = privKey.getPublicKeyPacket(); + BCPGKey privPk = privKey.getPrivateKeyDataPacket(); + + try + { + KeyFactory fact; + + switch (pubPk.getAlgorithm()) + { + case PGPPublicKey.RSA_ENCRYPT: + case PGPPublicKey.RSA_GENERAL: + case PGPPublicKey.RSA_SIGN: + RSAPublicBCPGKey rsaPub = (RSAPublicBCPGKey)pubPk.getKey(); + RSASecretBCPGKey rsaPriv = (RSASecretBCPGKey)privPk; + RSAPrivateCrtKeySpec rsaPrivSpec = new RSAPrivateCrtKeySpec( + rsaPriv.getModulus(), + rsaPub.getPublicExponent(), + rsaPriv.getPrivateExponent(), + rsaPriv.getPrimeP(), + rsaPriv.getPrimeQ(), + rsaPriv.getPrimeExponentP(), + rsaPriv.getPrimeExponentQ(), + rsaPriv.getCrtCoefficient()); + + fact = helper.createKeyFactory("RSA"); + + return fact.generatePrivate(rsaPrivSpec); + case PGPPublicKey.DSA: + DSAPublicBCPGKey dsaPub = (DSAPublicBCPGKey)pubPk.getKey(); + DSASecretBCPGKey dsaPriv = (DSASecretBCPGKey)privPk; + DSAPrivateKeySpec dsaPrivSpec = + new DSAPrivateKeySpec(dsaPriv.getX(), dsaPub.getP(), dsaPub.getQ(), dsaPub.getG()); + + fact = helper.createKeyFactory("DSA"); + + return fact.generatePrivate(dsaPrivSpec); + case PublicKeyAlgorithmTags.ECDH: + ECDHPublicBCPGKey ecdhPub = (ECDHPublicBCPGKey)pubPk.getKey(); + ECSecretBCPGKey ecdhK = (ECSecretBCPGKey)privPk; + ECPrivateKeySpec ecDhSpec = new ECPrivateKeySpec( + ecdhK.getX(), + getX9Parameters(ecdhPub.getCurveOID())); + fact = helper.createKeyFactory("ECDH"); + + return fact.generatePrivate(ecDhSpec); + case PublicKeyAlgorithmTags.ECDSA: + ECDSAPublicBCPGKey ecdsaPub = (ECDSAPublicBCPGKey)pubPk.getKey(); + ECSecretBCPGKey ecdsaK = (ECSecretBCPGKey)privPk; + ECPrivateKeySpec ecDsaSpec = new ECPrivateKeySpec( + ecdsaK.getX(), + getX9Parameters(ecdsaPub.getCurveOID())); + fact = helper.createKeyFactory("ECDSA"); + + return fact.generatePrivate(ecDsaSpec); + case PGPPublicKey.ELGAMAL_ENCRYPT: + case PGPPublicKey.ELGAMAL_GENERAL: + ElGamalPublicBCPGKey elPub = (ElGamalPublicBCPGKey)pubPk.getKey(); + ElGamalSecretBCPGKey elPriv = (ElGamalSecretBCPGKey)privPk; + ElGamalPrivateKeySpec elSpec = new ElGamalPrivateKeySpec(elPriv.getX(), new ElGamalParameterSpec(elPub.getP(), elPub.getG())); + + fact = helper.createKeyFactory("ElGamal"); + + return fact.generatePrivate(elSpec); + default: + throw new PGPException("unknown public key algorithm encountered"); + } + } + catch (PGPException e) + { + throw e; + } + catch (Exception e) + { + throw new PGPException("Exception constructing key", e); + } + } + + /** + * Convert a PrivateKey into a PGPPrivateKey. + * + * @param pub the corresponding PGPPublicKey to privKey. + * @param privKey the private key for the key in pub. + * @return a PGPPrivateKey + * @throws PGPException + */ + public PGPPrivateKey getPGPPrivateKey(PGPPublicKey pub, PrivateKey privKey) + throws PGPException + { + BCPGKey privPk; + + switch (pub.getAlgorithm()) + { + case PGPPublicKey.RSA_ENCRYPT: + case PGPPublicKey.RSA_SIGN: + case PGPPublicKey.RSA_GENERAL: + RSAPrivateCrtKey rsK = (RSAPrivateCrtKey)privKey; + + privPk = new RSASecretBCPGKey(rsK.getPrivateExponent(), rsK.getPrimeP(), rsK.getPrimeQ()); + break; + case PGPPublicKey.DSA: + DSAPrivateKey dsK = (DSAPrivateKey)privKey; + + privPk = new DSASecretBCPGKey(dsK.getX()); + break; + case PGPPublicKey.ELGAMAL_ENCRYPT: + case PGPPublicKey.ELGAMAL_GENERAL: + ElGamalPrivateKey esK = (ElGamalPrivateKey)privKey; + + privPk = new ElGamalSecretBCPGKey(esK.getX()); + break; + case PGPPublicKey.EC: + case PGPPublicKey.ECDSA: + ECPrivateKey ecK = (ECPrivateKey)privKey; + + privPk = new ECSecretBCPGKey(ecK.getS()); + break; + default: + throw new PGPException("unknown key class"); + } + + return new PGPPrivateKey(pub.getKeyID(), pub.getPublicKeyPacket(), privPk); + } + + private ECParameterSpec getX9Parameters(ASN1ObjectIdentifier curveOid) + { + X9ECParameters x9 = CustomNamedCurves.getByOID(curveOid); + if (x9 == null) + { + x9 = ECNamedCurveTable.getByOID(curveOid); + } + + return new ECNamedCurveSpec(curveOid.getId(), x9.getCurve(), x9.getG(), x9.getN(), + x9.getH(), x9.getSeed()); + } +} diff --git a/pg/src/main/java/org/spongycastle/openpgp/operator/jcajce/JcaPGPKeyPair.java b/pg/src/main/java/org/spongycastle/openpgp/operator/jcajce/JcaPGPKeyPair.java new file mode 100644 index 00000000..b32d00d7 --- /dev/null +++ b/pg/src/main/java/org/spongycastle/openpgp/operator/jcajce/JcaPGPKeyPair.java @@ -0,0 +1,48 @@ +package org.spongycastle.openpgp.operator.jcajce; + +import java.security.KeyPair; +import java.security.PrivateKey; +import java.security.PublicKey; +import java.util.Date; + +import org.spongycastle.openpgp.PGPAlgorithmParameters; +import org.spongycastle.openpgp.PGPException; +import org.spongycastle.openpgp.PGPKeyPair; +import org.spongycastle.openpgp.PGPPrivateKey; +import org.spongycastle.openpgp.PGPPublicKey; + +public class JcaPGPKeyPair + extends PGPKeyPair +{ + private static PGPPublicKey getPublicKey(int algorithm, PublicKey pubKey, Date date) + throws PGPException + { + return new JcaPGPKeyConverter().getPGPPublicKey(algorithm, pubKey, date); + } + + private static PGPPublicKey getPublicKey(int algorithm, PGPAlgorithmParameters algorithmParameters, PublicKey pubKey, Date date) + throws PGPException + { + return new JcaPGPKeyConverter().getPGPPublicKey(algorithm, algorithmParameters, pubKey, date); + } + + private static PGPPrivateKey getPrivateKey(PGPPublicKey pub, PrivateKey privKey) + throws PGPException + { + return new JcaPGPKeyConverter().getPGPPrivateKey(pub, privKey); + } + + public JcaPGPKeyPair(int algorithm, KeyPair keyPair, Date date) + throws PGPException + { + this.pub = getPublicKey(algorithm, keyPair.getPublic(), date); + this.priv = getPrivateKey(this.pub, keyPair.getPrivate()); + } + + public JcaPGPKeyPair(int algorithm, PGPAlgorithmParameters parameters, KeyPair keyPair, Date date) + throws PGPException + { + this.pub = getPublicKey(algorithm, parameters, keyPair.getPublic(), date); + this.priv = getPrivateKey(this.pub, keyPair.getPrivate()); + } +} diff --git a/pg/src/main/java/org/spongycastle/openpgp/operator/jcajce/JcaPGPPrivateKey.java b/pg/src/main/java/org/spongycastle/openpgp/operator/jcajce/JcaPGPPrivateKey.java new file mode 100644 index 00000000..a22f4561 --- /dev/null +++ b/pg/src/main/java/org/spongycastle/openpgp/operator/jcajce/JcaPGPPrivateKey.java @@ -0,0 +1,34 @@ +package org.spongycastle.openpgp.operator.jcajce; + +import java.security.PrivateKey; + +import org.spongycastle.openpgp.PGPPrivateKey; +import org.spongycastle.openpgp.PGPPublicKey; + +/** + * A JCA PrivateKey carrier. Use this one if you're dealing with a hardware adapter. + */ +public class JcaPGPPrivateKey + extends PGPPrivateKey +{ + private final PrivateKey privateKey; + + public JcaPGPPrivateKey(long keyID, PrivateKey privateKey) + { + super(keyID, null, null); + + this.privateKey = privateKey; + } + + public JcaPGPPrivateKey(PGPPublicKey pubKey, PrivateKey privateKey) + { + super(pubKey.getKeyID(), pubKey.getPublicKeyPacket(), null); + + this.privateKey = privateKey; + } + + public PrivateKey getPrivateKey() + { + return privateKey; + } +} diff --git a/pg/src/main/java/org/spongycastle/openpgp/operator/jcajce/JcePBEDataDecryptorFactoryBuilder.java b/pg/src/main/java/org/spongycastle/openpgp/operator/jcajce/JcePBEDataDecryptorFactoryBuilder.java new file mode 100644 index 00000000..d0e73b24 --- /dev/null +++ b/pg/src/main/java/org/spongycastle/openpgp/operator/jcajce/JcePBEDataDecryptorFactoryBuilder.java @@ -0,0 +1,109 @@ +package org.spongycastle.openpgp.operator.jcajce; + +import java.security.Provider; + +import javax.crypto.Cipher; +import javax.crypto.spec.IvParameterSpec; +import javax.crypto.spec.SecretKeySpec; + +import org.spongycastle.jcajce.util.DefaultJcaJceHelper; +import org.spongycastle.jcajce.util.NamedJcaJceHelper; +import org.spongycastle.jcajce.util.ProviderJcaJceHelper; +import org.spongycastle.openpgp.PGPException; +import org.spongycastle.openpgp.operator.PBEDataDecryptorFactory; +import org.spongycastle.openpgp.operator.PGPDataDecryptor; +import org.spongycastle.openpgp.operator.PGPDigestCalculatorProvider; + +/** + * Builder for {@link PBEDataDecryptorFactory} instances that obtain cryptographic primitives using + * the JCE API. + */ +public class JcePBEDataDecryptorFactoryBuilder +{ + private OperatorHelper helper = new OperatorHelper(new DefaultJcaJceHelper()); + private PGPDigestCalculatorProvider calculatorProvider; + + /** + * Base constructor. + * + * @param calculatorProvider a digest calculator provider to provide calculators to support the key generation calculation required. + */ + public JcePBEDataDecryptorFactoryBuilder(PGPDigestCalculatorProvider calculatorProvider) + { + this.calculatorProvider = calculatorProvider; + } + + /** + * Set the provider object to use for creating cryptographic primitives in the resulting factory the builder produces. + * + * @param provider provider object for cryptographic primitives. + * @return the current builder. + */ + public JcePBEDataDecryptorFactoryBuilder setProvider(Provider provider) + { + this.helper = new OperatorHelper(new ProviderJcaJceHelper(provider)); + + return this; + } + + /** + * Set the provider name to use for creating cryptographic primitives in the resulting factory the builder produces. + * + * @param providerName the name of the provider to reference for cryptographic primitives. + * @return the current builder. + */ + public JcePBEDataDecryptorFactoryBuilder setProvider(String providerName) + { + this.helper = new OperatorHelper(new NamedJcaJceHelper(providerName)); + + return this; + } + + /** + * Construct a {@link PBEDataDecryptorFactory} to use to decrypt PBE encrypted data. + * + * @param passPhrase the pass phrase to use to generate keys in the resulting factory. + * @return a decryptor factory that can be used to generate PBE keys. + */ + public PBEDataDecryptorFactory build(char[] passPhrase) + { + return new PBEDataDecryptorFactory(passPhrase, calculatorProvider) + { + public byte[] recoverSessionData(int keyAlgorithm, byte[] key, byte[] secKeyData) + throws PGPException + { + try + { + if (secKeyData != null && secKeyData.length > 0) + { + String cipherName = PGPUtil.getSymmetricCipherName(keyAlgorithm); + Cipher keyCipher = helper.createCipher(cipherName + "/CFB/NoPadding"); + + keyCipher.init(Cipher.DECRYPT_MODE, new SecretKeySpec(key, cipherName), new IvParameterSpec(new byte[keyCipher.getBlockSize()])); + + return keyCipher.doFinal(secKeyData); + } + else + { + byte[] keyBytes = new byte[key.length + 1]; + + keyBytes[0] = (byte)keyAlgorithm; + System.arraycopy(key, 0, keyBytes, 1, key.length); + + return keyBytes; + } + } + catch (Exception e) + { + throw new PGPException("Exception recovering session info", e); + } + } + + public PGPDataDecryptor createDataDecryptor(boolean withIntegrityPacket, int encAlgorithm, byte[] key) + throws PGPException + { + return helper.createDataDecryptor(withIntegrityPacket, encAlgorithm, key); + } + }; + } +} diff --git a/pg/src/main/java/org/spongycastle/openpgp/operator/jcajce/JcePBEKeyEncryptionMethodGenerator.java b/pg/src/main/java/org/spongycastle/openpgp/operator/jcajce/JcePBEKeyEncryptionMethodGenerator.java new file mode 100644 index 00000000..510ca578 --- /dev/null +++ b/pg/src/main/java/org/spongycastle/openpgp/operator/jcajce/JcePBEKeyEncryptionMethodGenerator.java @@ -0,0 +1,142 @@ +package org.spongycastle.openpgp.operator.jcajce; + +import java.security.InvalidAlgorithmParameterException; +import java.security.InvalidKeyException; +import java.security.Provider; +import java.security.SecureRandom; + +import javax.crypto.BadPaddingException; +import javax.crypto.Cipher; +import javax.crypto.IllegalBlockSizeException; +import javax.crypto.SecretKey; +import javax.crypto.spec.IvParameterSpec; +import javax.crypto.spec.SecretKeySpec; + +import org.spongycastle.bcpg.S2K; +import org.spongycastle.jcajce.util.DefaultJcaJceHelper; +import org.spongycastle.jcajce.util.NamedJcaJceHelper; +import org.spongycastle.jcajce.util.ProviderJcaJceHelper; +import org.spongycastle.openpgp.PGPException; +import org.spongycastle.openpgp.operator.PBEKeyEncryptionMethodGenerator; +import org.spongycastle.openpgp.operator.PGPDigestCalculator; + +/** + * JCE based generator for password based encryption (PBE) data protection methods. + */ +public class JcePBEKeyEncryptionMethodGenerator + extends PBEKeyEncryptionMethodGenerator +{ + private OperatorHelper helper = new OperatorHelper(new DefaultJcaJceHelper()); + + /** + * Create a PBE encryption method generator using the provided digest and the default S2K count + * for key generation. + * + * @param passPhrase the passphrase to use as the primary source of key material. + * @param s2kDigestCalculator the digest calculator to use for key calculation. + */ + public JcePBEKeyEncryptionMethodGenerator(char[] passPhrase, PGPDigestCalculator s2kDigestCalculator) + { + super(passPhrase, s2kDigestCalculator); + } + + /** + * Create a PBE encryption method generator using the default SHA-1 digest and the default S2K + * count for key generation. + * + * @param passPhrase the passphrase to use as the primary source of key material. + */ + public JcePBEKeyEncryptionMethodGenerator(char[] passPhrase) + { + this(passPhrase, new SHA1PGPDigestCalculator()); + } + + /** + * Create a PBE encryption method generator using the provided calculator and S2K count for key + * generation. + * + * @param passPhrase the passphrase to use as the primary source of key material. + * @param s2kDigestCalculator the digest calculator to use for key calculation. + * @param s2kCount the single byte {@link S2K} count to use. + */ + public JcePBEKeyEncryptionMethodGenerator(char[] passPhrase, PGPDigestCalculator s2kDigestCalculator, int s2kCount) + { + super(passPhrase, s2kDigestCalculator, s2kCount); + } + + /** + * Create a PBE encryption method generator using the default SHA-1 digest calculator and a S2K + * count other than the default for key generation. + * + * @param passPhrase the passphrase to use as the primary source of key material. + * @param s2kCount the single byte {@link S2K} count to use. + */ + public JcePBEKeyEncryptionMethodGenerator(char[] passPhrase, int s2kCount) + { + super(passPhrase, new SHA1PGPDigestCalculator(), s2kCount); + } + + /** + * Sets the JCE provider to source cryptographic primitives from. + * + * @param provider the JCE provider to use. + * @return the current generator. + */ + public JcePBEKeyEncryptionMethodGenerator setProvider(Provider provider) + { + this.helper = new OperatorHelper(new ProviderJcaJceHelper(provider)); + + return this; + } + + /** + * Sets the JCE provider to source cryptographic primitives from. + * + * @param providerName the name of the JCE provider to use. + * @return the current generator. + */ + public JcePBEKeyEncryptionMethodGenerator setProvider(String providerName) + { + this.helper = new OperatorHelper(new NamedJcaJceHelper(providerName)); + + return this; + } + + public PBEKeyEncryptionMethodGenerator setSecureRandom(SecureRandom random) + { + super.setSecureRandom(random); + + return this; + } + + protected byte[] encryptSessionInfo(int encAlgorithm, byte[] key, byte[] sessionInfo) + throws PGPException + { + try + { + String cName = PGPUtil.getSymmetricCipherName(encAlgorithm); + Cipher c = helper.createCipher(cName + "/CFB/NoPadding"); + SecretKey sKey = new SecretKeySpec(key, PGPUtil.getSymmetricCipherName(encAlgorithm)); + + c.init(Cipher.ENCRYPT_MODE, sKey, new IvParameterSpec(new byte[c.getBlockSize()])); + + return c.doFinal(sessionInfo, 0, sessionInfo.length); + } + catch (IllegalBlockSizeException e) + { + throw new PGPException("illegal block size: " + e.getMessage(), e); + } + catch (BadPaddingException e) + { + throw new PGPException("bad padding: " + e.getMessage(), e); + } + catch (InvalidAlgorithmParameterException e) + { + throw new PGPException("IV invalid: " + e.getMessage(), e); + } + catch (InvalidKeyException e) + { + throw new PGPException("key invalid: " + e.getMessage(), e); + } + } +} diff --git a/pg/src/main/java/org/spongycastle/openpgp/operator/jcajce/JcePBEProtectionRemoverFactory.java b/pg/src/main/java/org/spongycastle/openpgp/operator/jcajce/JcePBEProtectionRemoverFactory.java new file mode 100644 index 00000000..1e8dc4b9 --- /dev/null +++ b/pg/src/main/java/org/spongycastle/openpgp/operator/jcajce/JcePBEProtectionRemoverFactory.java @@ -0,0 +1,106 @@ +package org.spongycastle.openpgp.operator.jcajce; + +import java.security.InvalidAlgorithmParameterException; +import java.security.InvalidKeyException; +import java.security.Provider; + +import javax.crypto.BadPaddingException; +import javax.crypto.Cipher; +import javax.crypto.IllegalBlockSizeException; +import javax.crypto.spec.IvParameterSpec; + +import org.spongycastle.jcajce.util.DefaultJcaJceHelper; +import org.spongycastle.jcajce.util.NamedJcaJceHelper; +import org.spongycastle.jcajce.util.ProviderJcaJceHelper; +import org.spongycastle.openpgp.PGPException; +import org.spongycastle.openpgp.operator.PBEProtectionRemoverFactory; +import org.spongycastle.openpgp.operator.PBESecretKeyDecryptor; +import org.spongycastle.openpgp.operator.PGPDigestCalculatorProvider; + +public class JcePBEProtectionRemoverFactory + implements PBEProtectionRemoverFactory +{ + private final char[] passPhrase; + + private OperatorHelper helper = new OperatorHelper(new DefaultJcaJceHelper()); + private PGPDigestCalculatorProvider calculatorProvider; + + private JcaPGPDigestCalculatorProviderBuilder calculatorProviderBuilder; + + public JcePBEProtectionRemoverFactory(char[] passPhrase) + { + this.passPhrase = passPhrase; + this.calculatorProviderBuilder = new JcaPGPDigestCalculatorProviderBuilder(); + } + + public JcePBEProtectionRemoverFactory(char[] passPhrase, PGPDigestCalculatorProvider calculatorProvider) + { + this.passPhrase = passPhrase; + this.calculatorProvider = calculatorProvider; + } + + public JcePBEProtectionRemoverFactory setProvider(Provider provider) + { + this.helper = new OperatorHelper(new ProviderJcaJceHelper(provider)); + + if (calculatorProviderBuilder != null) + { + calculatorProviderBuilder.setProvider(provider); + } + + return this; + } + + public JcePBEProtectionRemoverFactory setProvider(String providerName) + { + this.helper = new OperatorHelper(new NamedJcaJceHelper(providerName)); + + if (calculatorProviderBuilder != null) + { + calculatorProviderBuilder.setProvider(providerName); + } + + return this; + } + + public PBESecretKeyDecryptor createDecryptor(String protection) + throws PGPException + { + if (calculatorProvider == null) + { + calculatorProvider = calculatorProviderBuilder.build(); + } + + return new PBESecretKeyDecryptor(passPhrase, calculatorProvider) + { + public byte[] recoverKeyData(int encAlgorithm, byte[] key, byte[] iv, byte[] keyData, int keyOff, int keyLen) + throws PGPException + { + try + { + Cipher c = helper.createCipher(PGPUtil.getSymmetricCipherName(encAlgorithm) + "/CBC/NoPadding"); + + c.init(Cipher.DECRYPT_MODE, PGPUtil.makeSymmetricKey(encAlgorithm, key), new IvParameterSpec(iv)); + + return c.doFinal(keyData, keyOff, keyLen); + } + catch (IllegalBlockSizeException e) + { + throw new PGPException("illegal block size: " + e.getMessage(), e); + } + catch (BadPaddingException e) + { + throw new PGPException("bad padding: " + e.getMessage(), e); + } + catch (InvalidAlgorithmParameterException e) + { + throw new PGPException("invalid parameter: " + e.getMessage(), e); + } + catch (InvalidKeyException e) + { + throw new PGPException("invalid key: " + e.getMessage(), e); + } + } + }; + } +} diff --git a/pg/src/main/java/org/spongycastle/openpgp/operator/jcajce/JcePBESecretKeyDecryptorBuilder.java b/pg/src/main/java/org/spongycastle/openpgp/operator/jcajce/JcePBESecretKeyDecryptorBuilder.java new file mode 100644 index 00000000..15ab5358 --- /dev/null +++ b/pg/src/main/java/org/spongycastle/openpgp/operator/jcajce/JcePBESecretKeyDecryptorBuilder.java @@ -0,0 +1,100 @@ +package org.spongycastle.openpgp.operator.jcajce; + +import java.security.InvalidAlgorithmParameterException; +import java.security.InvalidKeyException; +import java.security.Provider; + +import javax.crypto.BadPaddingException; +import javax.crypto.Cipher; +import javax.crypto.IllegalBlockSizeException; +import javax.crypto.spec.IvParameterSpec; + +import org.spongycastle.jcajce.util.DefaultJcaJceHelper; +import org.spongycastle.jcajce.util.NamedJcaJceHelper; +import org.spongycastle.jcajce.util.ProviderJcaJceHelper; +import org.spongycastle.openpgp.PGPException; +import org.spongycastle.openpgp.operator.PBESecretKeyDecryptor; +import org.spongycastle.openpgp.operator.PGPDigestCalculatorProvider; + +public class JcePBESecretKeyDecryptorBuilder +{ + private OperatorHelper helper = new OperatorHelper(new DefaultJcaJceHelper()); + private PGPDigestCalculatorProvider calculatorProvider; + + private JcaPGPDigestCalculatorProviderBuilder calculatorProviderBuilder; + + public JcePBESecretKeyDecryptorBuilder() + { + this.calculatorProviderBuilder = new JcaPGPDigestCalculatorProviderBuilder(); + } + + public JcePBESecretKeyDecryptorBuilder(PGPDigestCalculatorProvider calculatorProvider) + { + this.calculatorProvider = calculatorProvider; + } + + public JcePBESecretKeyDecryptorBuilder setProvider(Provider provider) + { + this.helper = new OperatorHelper(new ProviderJcaJceHelper(provider)); + + if (calculatorProviderBuilder != null) + { + calculatorProviderBuilder.setProvider(provider); + } + + return this; + } + + public JcePBESecretKeyDecryptorBuilder setProvider(String providerName) + { + this.helper = new OperatorHelper(new NamedJcaJceHelper(providerName)); + + if (calculatorProviderBuilder != null) + { + calculatorProviderBuilder.setProvider(providerName); + } + + return this; + } + + public PBESecretKeyDecryptor build(char[] passPhrase) + throws PGPException + { + if (calculatorProvider == null) + { + calculatorProvider = calculatorProviderBuilder.build(); + } + + return new PBESecretKeyDecryptor(passPhrase, calculatorProvider) + { + public byte[] recoverKeyData(int encAlgorithm, byte[] key, byte[] iv, byte[] keyData, int keyOff, int keyLen) + throws PGPException + { + try + { + Cipher c = helper.createCipher(PGPUtil.getSymmetricCipherName(encAlgorithm) + "/CFB/NoPadding"); + + c.init(Cipher.DECRYPT_MODE, PGPUtil.makeSymmetricKey(encAlgorithm, key), new IvParameterSpec(iv)); + + return c.doFinal(keyData, keyOff, keyLen); + } + catch (IllegalBlockSizeException e) + { + throw new PGPException("illegal block size: " + e.getMessage(), e); + } + catch (BadPaddingException e) + { + throw new PGPException("bad padding: " + e.getMessage(), e); + } + catch (InvalidAlgorithmParameterException e) + { + throw new PGPException("invalid parameter: " + e.getMessage(), e); + } + catch (InvalidKeyException e) + { + throw new PGPException("invalid key: " + e.getMessage(), e); + } + } + }; + } +} diff --git a/pg/src/main/java/org/spongycastle/openpgp/operator/jcajce/JcePBESecretKeyEncryptorBuilder.java b/pg/src/main/java/org/spongycastle/openpgp/operator/jcajce/JcePBESecretKeyEncryptorBuilder.java new file mode 100644 index 00000000..4a137418 --- /dev/null +++ b/pg/src/main/java/org/spongycastle/openpgp/operator/jcajce/JcePBESecretKeyEncryptorBuilder.java @@ -0,0 +1,180 @@ +package org.spongycastle.openpgp.operator.jcajce; + +import java.security.InvalidAlgorithmParameterException; +import java.security.InvalidKeyException; +import java.security.Provider; +import java.security.SecureRandom; + +import javax.crypto.BadPaddingException; +import javax.crypto.Cipher; +import javax.crypto.IllegalBlockSizeException; +import javax.crypto.spec.IvParameterSpec; + +import org.spongycastle.jcajce.util.DefaultJcaJceHelper; +import org.spongycastle.jcajce.util.NamedJcaJceHelper; +import org.spongycastle.jcajce.util.ProviderJcaJceHelper; +import org.spongycastle.openpgp.PGPException; +import org.spongycastle.openpgp.operator.PBESecretKeyEncryptor; +import org.spongycastle.openpgp.operator.PGPDigestCalculator; + +public class JcePBESecretKeyEncryptorBuilder +{ + private OperatorHelper helper = new OperatorHelper(new DefaultJcaJceHelper()); + private int encAlgorithm; + private PGPDigestCalculator s2kDigestCalculator; + private SecureRandom random; + private int s2kCount = 0x60; + + public JcePBESecretKeyEncryptorBuilder(int encAlgorithm) + { + this(encAlgorithm, new SHA1PGPDigestCalculator()); + } + + /** + * Create a SecretKeyEncryptorBuilder with the S2K count different to the default of 0x60. + * + * @param encAlgorithm encryption algorithm to use. + * @param s2kCount iteration count to use for S2K function. + */ + public JcePBESecretKeyEncryptorBuilder(int encAlgorithm, int s2kCount) + { + this(encAlgorithm, new SHA1PGPDigestCalculator(), s2kCount); + } + + /** + * Create a builder which will make encryptors using the passed in digest calculator. If a MD5 calculator is + * passed in the builder will assume the encryptors are for use with version 3 keys. + * + * @param encAlgorithm encryption algorithm to use. + * @param s2kDigestCalculator digest calculator to use. + */ + public JcePBESecretKeyEncryptorBuilder(int encAlgorithm, PGPDigestCalculator s2kDigestCalculator) + { + this(encAlgorithm, s2kDigestCalculator, 0x60); + } + + /** + * Create an SecretKeyEncryptorBuilder with the S2k count different to the default of 0x60, and the S2K digest + * different from SHA-1. + * + * @param encAlgorithm encryption algorithm to use. + * @param s2kDigestCalculator digest calculator to use. + * @param s2kCount iteration count to use for S2K function. + */ + public JcePBESecretKeyEncryptorBuilder(int encAlgorithm, PGPDigestCalculator s2kDigestCalculator, int s2kCount) + { + this.encAlgorithm = encAlgorithm; + this.s2kDigestCalculator = s2kDigestCalculator; + + if (s2kCount < 0 || s2kCount > 0xff) + { + throw new IllegalArgumentException("s2KCount value outside of range 0 to 255."); + } + + this.s2kCount = s2kCount; + } + + public JcePBESecretKeyEncryptorBuilder setProvider(Provider provider) + { + this.helper = new OperatorHelper(new ProviderJcaJceHelper(provider)); + + return this; + } + + public JcePBESecretKeyEncryptorBuilder setProvider(String providerName) + { + this.helper = new OperatorHelper(new NamedJcaJceHelper(providerName)); + + return this; + } + + /** + * Provide a user defined source of randomness. + * + * @param random the secure random to be used. + * @return the current builder. + */ + public JcePBESecretKeyEncryptorBuilder setSecureRandom(SecureRandom random) + { + this.random = random; + + return this; + } + + public PBESecretKeyEncryptor build(char[] passPhrase) + { + if (random == null) + { + random = new SecureRandom(); + } + + return new PBESecretKeyEncryptor(encAlgorithm, s2kDigestCalculator, s2kCount, random, passPhrase) + { + private Cipher c; + private byte[] iv; + + public byte[] encryptKeyData(byte[] key, byte[] keyData, int keyOff, int keyLen) + throws PGPException + { + try + { + c = helper.createCipher(PGPUtil.getSymmetricCipherName(this.encAlgorithm) + "/CFB/NoPadding"); + + c.init(Cipher.ENCRYPT_MODE, PGPUtil.makeSymmetricKey(this.encAlgorithm, key), this.random); + + iv = c.getIV(); + + return c.doFinal(keyData, keyOff, keyLen); + } + catch (IllegalBlockSizeException e) + { + throw new PGPException("illegal block size: " + e.getMessage(), e); + } + catch (BadPaddingException e) + { + throw new PGPException("bad padding: " + e.getMessage(), e); + } + catch (InvalidKeyException e) + { + throw new PGPException("invalid key: " + e.getMessage(), e); + } + } + + public byte[] encryptKeyData(byte[] key, byte[] iv, byte[] keyData, int keyOff, int keyLen) + throws PGPException + { + try + { + c = helper.createCipher(PGPUtil.getSymmetricCipherName(this.encAlgorithm) + "/CFB/NoPadding"); + + c.init(Cipher.ENCRYPT_MODE, PGPUtil.makeSymmetricKey(this.encAlgorithm, key), new IvParameterSpec(iv)); + + this.iv = iv; + + return c.doFinal(keyData, keyOff, keyLen); + } + catch (IllegalBlockSizeException e) + { + throw new PGPException("illegal block size: " + e.getMessage(), e); + } + catch (BadPaddingException e) + { + throw new PGPException("bad padding: " + e.getMessage(), e); + } + catch (InvalidKeyException e) + { + throw new PGPException("invalid key: " + e.getMessage(), e); + } + catch (InvalidAlgorithmParameterException e) + { + throw new PGPException("invalid iv: " + e.getMessage(), e); + } + } + + public byte[] getCipherIV() + { + return iv; + } + }; + } +} diff --git a/pg/src/main/java/org/spongycastle/openpgp/operator/jcajce/JcePGPDataEncryptorBuilder.java b/pg/src/main/java/org/spongycastle/openpgp/operator/jcajce/JcePGPDataEncryptorBuilder.java new file mode 100644 index 00000000..6761e607 --- /dev/null +++ b/pg/src/main/java/org/spongycastle/openpgp/operator/jcajce/JcePGPDataEncryptorBuilder.java @@ -0,0 +1,175 @@ +package org.spongycastle.openpgp.operator.jcajce; + +import java.io.OutputStream; +import java.security.InvalidAlgorithmParameterException; +import java.security.InvalidKeyException; +import java.security.Provider; +import java.security.SecureRandom; + +import javax.crypto.Cipher; +import javax.crypto.CipherOutputStream; +import javax.crypto.spec.IvParameterSpec; + +import org.spongycastle.bcpg.SymmetricKeyAlgorithmTags; +import org.spongycastle.jcajce.util.DefaultJcaJceHelper; +import org.spongycastle.jcajce.util.NamedJcaJceHelper; +import org.spongycastle.jcajce.util.ProviderJcaJceHelper; +import org.spongycastle.openpgp.PGPException; +import org.spongycastle.openpgp.operator.PGPDataEncryptor; +import org.spongycastle.openpgp.operator.PGPDataEncryptorBuilder; +import org.spongycastle.openpgp.operator.PGPDigestCalculator; + +/** + * {@link PGPDataEncryptorBuilder} implementation that sources cryptographic primitives using the + * JCE APIs. + * <p/> + * By default, cryptographic primitives will be loaded using the default JCE load order (i.e. + * without specifying a provider). <br/> + * A specific provider can be specified using one of the {@link #setProvider(String)} methods. + */ +public class JcePGPDataEncryptorBuilder + implements PGPDataEncryptorBuilder +{ + private OperatorHelper helper = new OperatorHelper(new DefaultJcaJceHelper()); + private SecureRandom random; + private boolean withIntegrityPacket; + private int encAlgorithm; + + /** + * Constructs a new data encryptor builder for a specified cipher type. + * + * @param encAlgorithm one of the {@link SymmetricKeyAlgorithmTags supported symmetric cipher + * algorithms}. May not be {@link SymmetricKeyAlgorithmTags#NULL}. + */ + public JcePGPDataEncryptorBuilder(int encAlgorithm) + { + this.encAlgorithm = encAlgorithm; + + if (encAlgorithm == 0) + { + throw new IllegalArgumentException("null cipher specified"); + } + } + + /** + * Sets whether or not the resulting encrypted data will be protected using an integrity packet. + * + * @param withIntegrityPacket true if an integrity packet is to be included, false otherwise. + * @return the current builder. + */ + public JcePGPDataEncryptorBuilder setWithIntegrityPacket(boolean withIntegrityPacket) + { + this.withIntegrityPacket = withIntegrityPacket; + + return this; + } + + /** + * Sets the JCE provider to source cryptographic primitives from. + * + * @param provider the JCE provider to use. + * @return the current builder. + */ + public JcePGPDataEncryptorBuilder setProvider(Provider provider) + { + this.helper = new OperatorHelper(new ProviderJcaJceHelper(provider)); + + return this; + } + + /** + * Sets the JCE provider to source cryptographic primitives from. + * + * @param providerName the name of the JCE provider to use. + * @return the current builder. + */ + public JcePGPDataEncryptorBuilder setProvider(String providerName) + { + this.helper = new OperatorHelper(new NamedJcaJceHelper(providerName)); + + return this; + } + + /** + * Provide a user defined source of randomness. + * <p/> + * If no SecureRandom is configured, a default SecureRandom will be used. + * + * @param random the secure random to be used. + * @return the current builder. + */ + public JcePGPDataEncryptorBuilder setSecureRandom(SecureRandom random) + { + this.random = random; + + return this; + } + + public int getAlgorithm() + { + return encAlgorithm; + } + + public SecureRandom getSecureRandom() + { + if (random == null) + { + random = new SecureRandom(); + } + + return random; + } + + public PGPDataEncryptor build(byte[] keyBytes) + throws PGPException + { + return new MyPGPDataEncryptor(keyBytes); + } + + private class MyPGPDataEncryptor + implements PGPDataEncryptor + { + private final Cipher c; + + MyPGPDataEncryptor(byte[] keyBytes) + throws PGPException + { + c = helper.createStreamCipher(encAlgorithm, withIntegrityPacket); + + byte[] iv = new byte[c.getBlockSize()]; + + try + { + c.init(Cipher.ENCRYPT_MODE, PGPUtil.makeSymmetricKey(encAlgorithm, keyBytes), new IvParameterSpec(iv)); + } + catch (InvalidKeyException e) + { + throw new PGPException("invalid key: " + e.getMessage(), e); + } + catch (InvalidAlgorithmParameterException e) + { + throw new PGPException("imvalid algorithm parameter: " + e.getMessage(), e); + } + } + + public OutputStream getOutputStream(OutputStream out) + { + return new CipherOutputStream(out, c); + } + + public PGPDigestCalculator getIntegrityCalculator() + { + if (withIntegrityPacket) + { + return new SHA1PGPDigestCalculator(); + } + + return null; + } + + public int getBlockSize() + { + return c.getBlockSize(); + } + } +} diff --git a/pg/src/main/java/org/spongycastle/openpgp/operator/jcajce/JcePublicKeyDataDecryptorFactoryBuilder.java b/pg/src/main/java/org/spongycastle/openpgp/operator/jcajce/JcePublicKeyDataDecryptorFactoryBuilder.java new file mode 100644 index 00000000..51534ba9 --- /dev/null +++ b/pg/src/main/java/org/spongycastle/openpgp/operator/jcajce/JcePublicKeyDataDecryptorFactoryBuilder.java @@ -0,0 +1,239 @@ +package org.spongycastle.openpgp.operator.jcajce; + +import java.security.InvalidKeyException; +import java.security.Key; +import java.security.NoSuchAlgorithmException; +import java.security.PrivateKey; +import java.security.Provider; + +import javax.crypto.Cipher; +import javax.crypto.spec.SecretKeySpec; + +import org.spongycastle.asn1.nist.NISTNamedCurves; +import org.spongycastle.asn1.x9.X9ECParameters; +import org.spongycastle.bcpg.BCPGKey; +import org.spongycastle.bcpg.ECDHPublicBCPGKey; +import org.spongycastle.bcpg.ECSecretBCPGKey; +import org.spongycastle.bcpg.PublicKeyAlgorithmTags; +import org.spongycastle.bcpg.PublicKeyPacket; +import org.spongycastle.jcajce.util.DefaultJcaJceHelper; +import org.spongycastle.jcajce.util.NamedJcaJceHelper; +import org.spongycastle.jcajce.util.ProviderJcaJceHelper; +import org.spongycastle.jce.interfaces.ElGamalKey; +import org.spongycastle.math.ec.ECPoint; +import org.spongycastle.openpgp.PGPException; +import org.spongycastle.openpgp.PGPPrivateKey; +import org.spongycastle.openpgp.PGPPublicKey; +import org.spongycastle.openpgp.operator.PGPDataDecryptor; +import org.spongycastle.openpgp.operator.PGPPad; +import org.spongycastle.openpgp.operator.PublicKeyDataDecryptorFactory; +import org.spongycastle.openpgp.operator.RFC6637KDFCalculator; + +public class JcePublicKeyDataDecryptorFactoryBuilder +{ + private OperatorHelper helper = new OperatorHelper(new DefaultJcaJceHelper()); + private OperatorHelper contentHelper = new OperatorHelper(new DefaultJcaJceHelper()); + private JcaPGPKeyConverter keyConverter = new JcaPGPKeyConverter(); + private JcaPGPDigestCalculatorProviderBuilder digestCalculatorProviderBuilder = new JcaPGPDigestCalculatorProviderBuilder(); + private JcaKeyFingerprintCalculator fingerprintCalculator = new JcaKeyFingerprintCalculator(); + + public JcePublicKeyDataDecryptorFactoryBuilder() + { + } + + /** + * Set the provider object to use for creating cryptographic primitives in the resulting factory the builder produces. + * + * @param provider provider object for cryptographic primitives. + * @return the current builder. + */ + public JcePublicKeyDataDecryptorFactoryBuilder setProvider(Provider provider) + { + this.helper = new OperatorHelper(new ProviderJcaJceHelper(provider)); + keyConverter.setProvider(provider); + this.contentHelper = helper; + + return this; + } + + /** + * Set the provider name to use for creating cryptographic primitives in the resulting factory the builder produces. + * + * @param providerName the name of the provider to reference for cryptographic primitives. + * @return the current builder. + */ + public JcePublicKeyDataDecryptorFactoryBuilder setProvider(String providerName) + { + this.helper = new OperatorHelper(new NamedJcaJceHelper(providerName)); + keyConverter.setProvider(providerName); + this.contentHelper = helper; + + return this; + } + + public JcePublicKeyDataDecryptorFactoryBuilder setContentProvider(Provider provider) + { + this.contentHelper = new OperatorHelper(new ProviderJcaJceHelper(provider)); + + return this; + } + + public JcePublicKeyDataDecryptorFactoryBuilder setContentProvider(String providerName) + { + this.contentHelper = new OperatorHelper(new NamedJcaJceHelper(providerName)); + + return this; + } + + public PublicKeyDataDecryptorFactory build(final PrivateKey privKey) + { + return new PublicKeyDataDecryptorFactory() + { + public byte[] recoverSessionData(int keyAlgorithm, byte[][] secKeyData) + throws PGPException + { + if (keyAlgorithm == PublicKeyAlgorithmTags.ECDH) + { + throw new PGPException("ECDH requires use of PGPPrivateKey for decryption"); + } + return decryptSessionData(keyAlgorithm, privKey, secKeyData); + } + + public PGPDataDecryptor createDataDecryptor(boolean withIntegrityPacket, int encAlgorithm, byte[] key) + throws PGPException + { + return contentHelper.createDataDecryptor(withIntegrityPacket, encAlgorithm, key); + } + }; + } + + public PublicKeyDataDecryptorFactory build(final PGPPrivateKey privKey) + { + return new PublicKeyDataDecryptorFactory() + { + public byte[] recoverSessionData(int keyAlgorithm, byte[][] secKeyData) + throws PGPException + { + if (keyAlgorithm == PublicKeyAlgorithmTags.ECDH) + { + return decryptSessionData(privKey.getPrivateKeyDataPacket(), privKey.getPublicKeyPacket(), secKeyData); + } + + return decryptSessionData(keyAlgorithm, keyConverter.getPrivateKey(privKey), secKeyData); + } + + public PGPDataDecryptor createDataDecryptor(boolean withIntegrityPacket, int encAlgorithm, byte[] key) + throws PGPException + { + return contentHelper.createDataDecryptor(withIntegrityPacket, encAlgorithm, key); + } + }; + } + + private byte[] decryptSessionData(BCPGKey privateKeyPacket, PublicKeyPacket pubKeyData, byte[][] secKeyData) + throws PGPException + { + ECDHPublicBCPGKey ecKey = (ECDHPublicBCPGKey)pubKeyData.getKey(); + X9ECParameters x9Params = NISTNamedCurves.getByOID(ecKey.getCurveOID()); + + byte[] enc = secKeyData[0]; + + int pLen = ((((enc[0] & 0xff) << 8) + (enc[1] & 0xff)) + 7) / 8; + byte[] pEnc = new byte[pLen]; + + System.arraycopy(enc, 2, pEnc, 0, pLen); + + byte[] keyEnc = new byte[enc[pLen + 2]]; + + System.arraycopy(enc, 2 + pLen + 1, keyEnc, 0, keyEnc.length); + + Cipher c = helper.createKeyWrapper(ecKey.getSymmetricKeyAlgorithm()); + + ECPoint S = x9Params.getCurve().decodePoint(pEnc).multiply(((ECSecretBCPGKey)privateKeyPacket).getX()).normalize(); + + RFC6637KDFCalculator rfc6637KDFCalculator = new RFC6637KDFCalculator(digestCalculatorProviderBuilder.build().get(ecKey.getHashAlgorithm()), ecKey.getSymmetricKeyAlgorithm()); + Key key = new SecretKeySpec(rfc6637KDFCalculator.createKey(ecKey.getCurveOID(), S, fingerprintCalculator.calculateFingerprint(pubKeyData)), "AESWrap"); + + try + { + c.init(Cipher.UNWRAP_MODE, key); + + Key paddedSessionKey = c.unwrap(keyEnc, "Session", Cipher.SECRET_KEY); + + return PGPPad.unpadSessionData(paddedSessionKey.getEncoded()); + } + catch (InvalidKeyException e) + { + throw new PGPException("error setting asymmetric cipher", e); + } + catch (NoSuchAlgorithmException e) + { + throw new PGPException("error setting asymmetric cipher", e); + } + } + + private byte[] decryptSessionData(int keyAlgorithm, PrivateKey privKey, byte[][] secKeyData) + throws PGPException + { + Cipher c1 = helper.createPublicKeyCipher(keyAlgorithm); + + try + { + c1.init(Cipher.DECRYPT_MODE, privKey); + } + catch (InvalidKeyException e) + { + throw new PGPException("error setting asymmetric cipher", e); + } + + if (keyAlgorithm == PGPPublicKey.RSA_ENCRYPT + || keyAlgorithm == PGPPublicKey.RSA_GENERAL) + { + byte[] bi = secKeyData[0]; // encoded MPI + + c1.update(bi, 2, bi.length - 2); + } + else + { + ElGamalKey k = (ElGamalKey)privKey; + int size = (k.getParameters().getP().bitLength() + 7) / 8; + byte[] tmp = new byte[size]; + + byte[] bi = secKeyData[0]; // encoded MPI + if (bi.length - 2 > size) // leading Zero? Shouldn't happen but... + { + c1.update(bi, 3, bi.length - 3); + } + else + { + System.arraycopy(bi, 2, tmp, tmp.length - (bi.length - 2), bi.length - 2); + c1.update(tmp); + } + + bi = secKeyData[1]; // encoded MPI + for (int i = 0; i != tmp.length; i++) + { + tmp[i] = 0; + } + + if (bi.length - 2 > size) // leading Zero? Shouldn't happen but... + { + c1.update(bi, 3, bi.length - 3); + } + else + { + System.arraycopy(bi, 2, tmp, tmp.length - (bi.length - 2), bi.length - 2); + c1.update(tmp); + } + } + + try + { + return c1.doFinal(); + } + catch (Exception e) + { + throw new PGPException("exception decrypting session data", e); + } + } +} diff --git a/pg/src/main/java/org/spongycastle/openpgp/operator/jcajce/JcePublicKeyKeyEncryptionMethodGenerator.java b/pg/src/main/java/org/spongycastle/openpgp/operator/jcajce/JcePublicKeyKeyEncryptionMethodGenerator.java new file mode 100644 index 00000000..bd7f4999 --- /dev/null +++ b/pg/src/main/java/org/spongycastle/openpgp/operator/jcajce/JcePublicKeyKeyEncryptionMethodGenerator.java @@ -0,0 +1,166 @@ +package org.spongycastle.openpgp.operator.jcajce; + +import java.io.IOException; +import java.math.BigInteger; +import java.security.InvalidKeyException; +import java.security.Key; +import java.security.Provider; +import java.security.SecureRandom; + +import javax.crypto.BadPaddingException; +import javax.crypto.Cipher; +import javax.crypto.IllegalBlockSizeException; +import javax.crypto.spec.SecretKeySpec; + +import org.spongycastle.asn1.nist.NISTNamedCurves; +import org.spongycastle.asn1.x9.X9ECParameters; +import org.spongycastle.bcpg.ECDHPublicBCPGKey; +import org.spongycastle.bcpg.MPInteger; +import org.spongycastle.bcpg.PublicKeyAlgorithmTags; +import org.spongycastle.crypto.EphemeralKeyPair; +import org.spongycastle.crypto.KeyEncoder; +import org.spongycastle.crypto.generators.ECKeyPairGenerator; +import org.spongycastle.crypto.generators.EphemeralKeyPairGenerator; +import org.spongycastle.crypto.params.AsymmetricKeyParameter; +import org.spongycastle.crypto.params.ECDomainParameters; +import org.spongycastle.crypto.params.ECKeyGenerationParameters; +import org.spongycastle.crypto.params.ECPrivateKeyParameters; +import org.spongycastle.crypto.params.ECPublicKeyParameters; +import org.spongycastle.jcajce.util.DefaultJcaJceHelper; +import org.spongycastle.jcajce.util.NamedJcaJceHelper; +import org.spongycastle.jcajce.util.ProviderJcaJceHelper; +import org.spongycastle.math.ec.ECPoint; +import org.spongycastle.openpgp.PGPException; +import org.spongycastle.openpgp.PGPPublicKey; +import org.spongycastle.openpgp.operator.PGPPad; +import org.spongycastle.openpgp.operator.PublicKeyKeyEncryptionMethodGenerator; +import org.spongycastle.openpgp.operator.RFC6637KDFCalculator; + +public class JcePublicKeyKeyEncryptionMethodGenerator + extends PublicKeyKeyEncryptionMethodGenerator +{ + private OperatorHelper helper = new OperatorHelper(new DefaultJcaJceHelper()); + private SecureRandom random; + private JcaPGPKeyConverter keyConverter = new JcaPGPKeyConverter(); + private JcaPGPDigestCalculatorProviderBuilder digestCalculatorProviderBuilder = new JcaPGPDigestCalculatorProviderBuilder(); + + /** + * Create a public key encryption method generator with the method to be based on the passed in key. + * + * @param key the public key to use for encryption. + */ + public JcePublicKeyKeyEncryptionMethodGenerator(PGPPublicKey key) + { + super(key); + } + + public JcePublicKeyKeyEncryptionMethodGenerator setProvider(Provider provider) + { + this.helper = new OperatorHelper(new ProviderJcaJceHelper(provider)); + + keyConverter.setProvider(provider); + + return this; + } + + public JcePublicKeyKeyEncryptionMethodGenerator setProvider(String providerName) + { + this.helper = new OperatorHelper(new NamedJcaJceHelper(providerName)); + + keyConverter.setProvider(providerName); + + return this; + } + + /** + * Provide a user defined source of randomness. + * + * @param random the secure random to be used. + * @return the current generator. + */ + public JcePublicKeyKeyEncryptionMethodGenerator setSecureRandom(SecureRandom random) + { + this.random = random; + + return this; + } + + protected byte[] encryptSessionInfo(PGPPublicKey pubKey, byte[] sessionInfo) + throws PGPException + { + try + { + if (pubKey.getAlgorithm() == PublicKeyAlgorithmTags.ECDH) + { + ECDHPublicBCPGKey ecKey = (ECDHPublicBCPGKey)pubKey.getPublicKeyPacket().getKey(); + X9ECParameters x9Params = NISTNamedCurves.getByOID(ecKey.getCurveOID()); + ECDomainParameters ecParams = new ECDomainParameters(x9Params.getCurve(), x9Params.getG(), x9Params.getN()); + + // Generate the ephemeral key pair + ECKeyPairGenerator gen = new ECKeyPairGenerator(); + gen.init(new ECKeyGenerationParameters(ecParams, random)); + + EphemeralKeyPairGenerator kGen = new EphemeralKeyPairGenerator(gen, new KeyEncoder() + { + public byte[] getEncoded(AsymmetricKeyParameter keyParameter) + { + return ((ECPublicKeyParameters)keyParameter).getQ().getEncoded(false); + } + }); + + EphemeralKeyPair ephKp = kGen.generate(); + + ECPrivateKeyParameters ephPriv = (ECPrivateKeyParameters)ephKp.getKeyPair().getPrivate(); + + ECPoint S = ecKey.getPoint().multiply(ephPriv.getD()).normalize(); + + RFC6637KDFCalculator rfc6637KDFCalculator = new RFC6637KDFCalculator(digestCalculatorProviderBuilder.build().get(ecKey.getHashAlgorithm()), ecKey.getSymmetricKeyAlgorithm()); + + Key key = new SecretKeySpec(rfc6637KDFCalculator.createKey(ecKey.getCurveOID(), S, pubKey.getFingerprint()), "AESWrap"); + + Cipher c = helper.createKeyWrapper(ecKey.getSymmetricKeyAlgorithm()); + + c.init(Cipher.WRAP_MODE, key, random); + + byte[] paddedSessionData = PGPPad.padSessionData(sessionInfo); + + byte[] C = c.wrap(new SecretKeySpec(paddedSessionData, PGPUtil.getSymmetricCipherName(sessionInfo[0]))); + byte[] VB = new MPInteger(new BigInteger(1, ephKp.getEncodedPublicKey())).getEncoded(); + + byte[] rv = new byte[VB.length + 1 + C.length]; + + System.arraycopy(VB, 0, rv, 0, VB.length); + rv[VB.length] = (byte)C.length; + System.arraycopy(C, 0, rv, VB.length + 1, C.length); + + return rv; + } + else + { + Cipher c = helper.createPublicKeyCipher(pubKey.getAlgorithm()); + + Key key = keyConverter.getPublicKey(pubKey); + + c.init(Cipher.ENCRYPT_MODE, key, random); + + return c.doFinal(sessionInfo); + } + } + catch (IllegalBlockSizeException e) + { + throw new PGPException("illegal block size: " + e.getMessage(), e); + } + catch (BadPaddingException e) + { + throw new PGPException("bad padding: " + e.getMessage(), e); + } + catch (InvalidKeyException e) + { + throw new PGPException("key invalid: " + e.getMessage(), e); + } + catch (IOException e) + { + throw new PGPException("unable to encode MPI: " + e.getMessage(), e); + } + } +} diff --git a/pg/src/main/java/org/spongycastle/openpgp/operator/jcajce/OperatorHelper.java b/pg/src/main/java/org/spongycastle/openpgp/operator/jcajce/OperatorHelper.java new file mode 100644 index 00000000..38e07cd0 --- /dev/null +++ b/pg/src/main/java/org/spongycastle/openpgp/operator/jcajce/OperatorHelper.java @@ -0,0 +1,200 @@ +package org.spongycastle.openpgp.operator.jcajce; + +import java.io.InputStream; +import java.security.GeneralSecurityException; +import java.security.KeyFactory; +import java.security.MessageDigest; +import java.security.Signature; + +import javax.crypto.Cipher; +import javax.crypto.CipherInputStream; +import javax.crypto.SecretKey; +import javax.crypto.spec.IvParameterSpec; +import javax.crypto.spec.SecretKeySpec; + +import org.spongycastle.bcpg.PublicKeyAlgorithmTags; +import org.spongycastle.bcpg.SymmetricKeyAlgorithmTags; +import org.spongycastle.jcajce.util.JcaJceHelper; +import org.spongycastle.openpgp.PGPException; +import org.spongycastle.openpgp.PGPPublicKey; +import org.spongycastle.openpgp.operator.PGPDataDecryptor; +import org.spongycastle.openpgp.operator.PGPDigestCalculator; + +class OperatorHelper +{ + private JcaJceHelper helper; + + OperatorHelper(JcaJceHelper helper) + { + this.helper = helper; + } + + MessageDigest createDigest(int algorithm) + throws GeneralSecurityException, PGPException + { + MessageDigest dig; + + dig = helper.createDigest(PGPUtil.getDigestName(algorithm)); + + return dig; + } + + KeyFactory createKeyFactory(String algorithm) + throws GeneralSecurityException, PGPException + { + return helper.createKeyFactory(algorithm); + } + + PGPDataDecryptor createDataDecryptor(boolean withIntegrityPacket, int encAlgorithm, byte[] key) + throws PGPException + { + try + { + SecretKey secretKey = new SecretKeySpec(key, PGPUtil.getSymmetricCipherName(encAlgorithm)); + + final Cipher c = createStreamCipher(encAlgorithm, withIntegrityPacket); + + byte[] iv = new byte[c.getBlockSize()]; + + c.init(Cipher.DECRYPT_MODE, secretKey, new IvParameterSpec(iv)); + + return new PGPDataDecryptor() + { + public InputStream getInputStream(InputStream in) + { + return new CipherInputStream(in, c); + } + + public int getBlockSize() + { + return c.getBlockSize(); + } + + public PGPDigestCalculator getIntegrityCalculator() + { + return new SHA1PGPDigestCalculator(); + } + }; + } + catch (PGPException e) + { + throw e; + } + catch (Exception e) + { + throw new PGPException("Exception creating cipher", e); + } + } + + Cipher createStreamCipher(int encAlgorithm, boolean withIntegrityPacket) + throws PGPException + { + String mode = (withIntegrityPacket) + ? "CFB" + : "OpenPGPCFB"; + + String cName = PGPUtil.getSymmetricCipherName(encAlgorithm) + + "/" + mode + "/NoPadding"; + + return createCipher(cName); + } + + Cipher createCipher(String cipherName) + throws PGPException + { + try + { + return helper.createCipher(cipherName); + } + catch (GeneralSecurityException e) + { + throw new PGPException("cannot create cipher: " + e.getMessage(), e); + } + } + + Cipher createPublicKeyCipher(int encAlgorithm) + throws PGPException + { + switch (encAlgorithm) + { + case PGPPublicKey.RSA_ENCRYPT: + case PGPPublicKey.RSA_GENERAL: + return createCipher("RSA/ECB/PKCS1Padding"); + case PGPPublicKey.ELGAMAL_ENCRYPT: + case PGPPublicKey.ELGAMAL_GENERAL: + return createCipher("ElGamal/ECB/PKCS1Padding"); + case PGPPublicKey.DSA: + throw new PGPException("Can't use DSA for encryption."); + case PGPPublicKey.ECDSA: + throw new PGPException("Can't use ECDSA for encryption."); + default: + throw new PGPException("unknown asymmetric algorithm: " + encAlgorithm); + } + } + + Cipher createKeyWrapper(int encAlgorithm) + throws PGPException + { + try + { + switch (encAlgorithm) + { + case SymmetricKeyAlgorithmTags.AES_128: + case SymmetricKeyAlgorithmTags.AES_192: + case SymmetricKeyAlgorithmTags.AES_256: + return helper.createCipher("AESWrap"); + case SymmetricKeyAlgorithmTags.CAMELLIA_128: + case SymmetricKeyAlgorithmTags.CAMELLIA_192: + case SymmetricKeyAlgorithmTags.CAMELLIA_256: + return helper.createCipher("CamelliaWrap"); + default: + throw new PGPException("unknown wrap algorithm: " + encAlgorithm); + } + } + catch (GeneralSecurityException e) + { + throw new PGPException("cannot create cipher: " + e.getMessage(), e); + } + } + + private Signature createSignature(String cipherName) + throws PGPException + { + try + { + return helper.createSignature(cipherName); + } + catch (GeneralSecurityException e) + { + throw new PGPException("cannot create signature: " + e.getMessage(), e); + } + } + + public Signature createSignature(int keyAlgorithm, int hashAlgorithm) + throws PGPException + { + String encAlg; + + switch (keyAlgorithm) + { + case PublicKeyAlgorithmTags.RSA_GENERAL: + case PublicKeyAlgorithmTags.RSA_SIGN: + encAlg = "RSA"; + break; + case PublicKeyAlgorithmTags.DSA: + encAlg = "DSA"; + break; + case PublicKeyAlgorithmTags.ELGAMAL_ENCRYPT: // in some malformed cases. + case PublicKeyAlgorithmTags.ELGAMAL_GENERAL: + encAlg = "ElGamal"; + break; + case PublicKeyAlgorithmTags.ECDSA: + encAlg = "ECDSA"; + break; + default: + throw new PGPException("unknown algorithm tag in signature:" + keyAlgorithm); + } + + return createSignature(PGPUtil.getDigestName(hashAlgorithm) + "with" + encAlg); + } +} diff --git a/pg/src/main/java/org/spongycastle/openpgp/operator/jcajce/PGPUtil.java b/pg/src/main/java/org/spongycastle/openpgp/operator/jcajce/PGPUtil.java new file mode 100644 index 00000000..5edbdb82 --- /dev/null +++ b/pg/src/main/java/org/spongycastle/openpgp/operator/jcajce/PGPUtil.java @@ -0,0 +1,124 @@ +package org.spongycastle.openpgp.operator.jcajce; + +import javax.crypto.SecretKey; +import javax.crypto.spec.SecretKeySpec; + +import org.spongycastle.bcpg.HashAlgorithmTags; +import org.spongycastle.bcpg.PublicKeyAlgorithmTags; +import org.spongycastle.bcpg.SymmetricKeyAlgorithmTags; +import org.spongycastle.openpgp.PGPException; + +/** + * Basic utility class + */ +class PGPUtil +{ + static String getDigestName( + int hashAlgorithm) + throws PGPException + { + switch (hashAlgorithm) + { + case HashAlgorithmTags.SHA1: + return "SHA1"; + case HashAlgorithmTags.MD2: + return "MD2"; + case HashAlgorithmTags.MD5: + return "MD5"; + case HashAlgorithmTags.RIPEMD160: + return "RIPEMD160"; + case HashAlgorithmTags.SHA256: + return "SHA256"; + case HashAlgorithmTags.SHA384: + return "SHA384"; + case HashAlgorithmTags.SHA512: + return "SHA512"; + case HashAlgorithmTags.SHA224: + return "SHA224"; + case HashAlgorithmTags.TIGER_192: + return "TIGER"; + default: + throw new PGPException("unknown hash algorithm tag in getDigestName: " + hashAlgorithm); + } + } + + static String getSignatureName( + int keyAlgorithm, + int hashAlgorithm) + throws PGPException + { + String encAlg; + + switch (keyAlgorithm) + { + case PublicKeyAlgorithmTags.RSA_GENERAL: + case PublicKeyAlgorithmTags.RSA_SIGN: + encAlg = "RSA"; + break; + case PublicKeyAlgorithmTags.DSA: + encAlg = "DSA"; + break; + case PublicKeyAlgorithmTags.ELGAMAL_ENCRYPT: // in some malformed cases. + case PublicKeyAlgorithmTags.ELGAMAL_GENERAL: + encAlg = "ElGamal"; + break; + default: + throw new PGPException("unknown algorithm tag in signature:" + keyAlgorithm); + } + + return getDigestName(hashAlgorithm) + "with" + encAlg; + } + + static String getSymmetricCipherName( + int algorithm) + { + switch (algorithm) + { + case SymmetricKeyAlgorithmTags.NULL: + return null; + case SymmetricKeyAlgorithmTags.TRIPLE_DES: + return "DESEDE"; + case SymmetricKeyAlgorithmTags.IDEA: + return "IDEA"; + case SymmetricKeyAlgorithmTags.CAST5: + return "CAST5"; + case SymmetricKeyAlgorithmTags.BLOWFISH: + return "Blowfish"; + case SymmetricKeyAlgorithmTags.SAFER: + return "SAFER"; + case SymmetricKeyAlgorithmTags.DES: + return "DES"; + case SymmetricKeyAlgorithmTags.AES_128: + return "AES"; + case SymmetricKeyAlgorithmTags.AES_192: + return "AES"; + case SymmetricKeyAlgorithmTags.AES_256: + return "AES"; + case SymmetricKeyAlgorithmTags.CAMELLIA_128: + return "Camellia"; + case SymmetricKeyAlgorithmTags.CAMELLIA_192: + return "Camellia"; + case SymmetricKeyAlgorithmTags.CAMELLIA_256: + return "Camellia"; + case SymmetricKeyAlgorithmTags.TWOFISH: + return "Twofish"; + default: + throw new IllegalArgumentException("unknown symmetric algorithm: " + algorithm); + } + } + + public static SecretKey makeSymmetricKey( + int algorithm, + byte[] keyBytes) + throws PGPException + { + String algName = getSymmetricCipherName(algorithm); + + if (algName == null) + { + throw new PGPException("unknown symmetric algorithm: " + algorithm); + } + + return new SecretKeySpec(keyBytes, algName); + } +} diff --git a/pg/src/main/java/org/spongycastle/openpgp/operator/jcajce/SHA1PGPDigestCalculator.java b/pg/src/main/java/org/spongycastle/openpgp/operator/jcajce/SHA1PGPDigestCalculator.java new file mode 100644 index 00000000..424770e6 --- /dev/null +++ b/pg/src/main/java/org/spongycastle/openpgp/operator/jcajce/SHA1PGPDigestCalculator.java @@ -0,0 +1,81 @@ +package org.spongycastle.openpgp.operator.jcajce; + +import java.io.IOException; +import java.io.OutputStream; +import java.security.MessageDigest; +import java.security.NoSuchAlgorithmException; + +import org.spongycastle.bcpg.HashAlgorithmTags; +import org.spongycastle.openpgp.operator.PGPDigestCalculator; + +class SHA1PGPDigestCalculator + implements PGPDigestCalculator +{ + private MessageDigest digest; + + SHA1PGPDigestCalculator() + { + try + { + digest = MessageDigest.getInstance("SHA1"); + } + catch (NoSuchAlgorithmException e) + { + throw new IllegalStateException("cannot find SHA-1: " + e.getMessage()); + } + } + + public int getAlgorithm() + { + return HashAlgorithmTags.SHA1; + } + + public OutputStream getOutputStream() + { + return new DigestOutputStream(digest); + } + + public byte[] getDigest() + { + return digest.digest(); + } + + public void reset() + { + digest.reset(); + } + + private class DigestOutputStream + extends OutputStream + { + private MessageDigest dig; + + DigestOutputStream(MessageDigest dig) + { + this.dig = dig; + } + + public void write(byte[] bytes, int off, int len) + throws IOException + { + dig.update(bytes, off, len); + } + + public void write(byte[] bytes) + throws IOException + { + dig.update(bytes); + } + + public void write(int b) + throws IOException + { + dig.update((byte)b); + } + + byte[] getDigest() + { + return dig.digest(); + } + } +} diff --git a/pg/src/main/java/org/spongycastle/openpgp/operator/jcajce/SignatureOutputStream.java b/pg/src/main/java/org/spongycastle/openpgp/operator/jcajce/SignatureOutputStream.java new file mode 100644 index 00000000..808de300 --- /dev/null +++ b/pg/src/main/java/org/spongycastle/openpgp/operator/jcajce/SignatureOutputStream.java @@ -0,0 +1,56 @@ +package org.spongycastle.openpgp.operator.jcajce; + +import java.io.IOException; +import java.io.OutputStream; +import java.security.Signature; +import java.security.SignatureException; + +class SignatureOutputStream + extends OutputStream +{ + private Signature sig; + + SignatureOutputStream(Signature sig) + { + this.sig = sig; + } + + public void write(byte[] bytes, int off, int len) + throws IOException + { + try + { + sig.update(bytes, off, len); + } + catch (SignatureException e) + { + throw new IOException("signature update caused exception: " + e.getMessage()); + } + } + + public void write(byte[] bytes) + throws IOException + { + try + { + sig.update(bytes); + } + catch (SignatureException e) + { + throw new IOException("signature update caused exception: " + e.getMessage()); + } + } + + public void write(int b) + throws IOException + { + try + { + sig.update((byte)b); + } + catch (SignatureException e) + { + throw new IOException("signature update caused exception: " + e.getMessage()); + } + } +} |