diff --git a/src/EmailModuleWithTemplates/EmailModuleWithTemplates.mpr b/src/EmailModuleWithTemplates/EmailModuleWithTemplates.mpr index d8403e8..3d67a2b 100644 Binary files a/src/EmailModuleWithTemplates/EmailModuleWithTemplates.mpr and b/src/EmailModuleWithTemplates/EmailModuleWithTemplates.mpr differ diff --git a/src/EmailModuleWithTemplates/javasource/encryption/actions/DecryptString.java b/src/EmailModuleWithTemplates/javasource/encryption/actions/DecryptString.java index d4331b2..c754450 100644 --- a/src/EmailModuleWithTemplates/javasource/encryption/actions/DecryptString.java +++ b/src/EmailModuleWithTemplates/javasource/encryption/actions/DecryptString.java @@ -16,6 +16,8 @@ import javax.crypto.spec.GCMParameterSpec; import javax.crypto.spec.IvParameterSpec; import javax.crypto.spec.SecretKeySpec; +import java.util.regex.Pattern; +import java.util.regex.Matcher; import com.mendix.systemwideinterfaces.MendixRuntimeException; import com.mendix.systemwideinterfaces.core.IContext; import com.mendix.webui.CustomJavaAction; @@ -25,38 +27,36 @@ public class DecryptString extends CustomJavaAction private java.lang.String value; private java.lang.String key; private java.lang.String prefix; + private java.lang.String legacyKey; - public DecryptString(IContext context, java.lang.String value, java.lang.String key, java.lang.String prefix) + public DecryptString(IContext context, java.lang.String value, java.lang.String key, java.lang.String prefix, java.lang.String legacyKey) { super(context); this.value = value; this.key = key; this.prefix = prefix; + this.legacyKey = legacyKey; } @java.lang.Override public java.lang.String executeAction() throws Exception { // BEGIN USER CODE - if (this.value == null || !isStartsWithRightPrefix()) - return null; - if (this.prefix == null || this.prefix.isEmpty()) - throw new MendixRuntimeException("Prefix should not be empty"); - if (this.key == null || this.key.isEmpty()) - throw new MendixRuntimeException("Key should not be empty"); - if (this.key.length() != 16) - throw new MendixRuntimeException("Key length should be 16"); - - String decryptedText = null; + if (this.prefix != null) + throw new MendixRuntimeException("Prefix should be null when passed to DecryptString, this parameter will be deprecated"); + if (this.value == null) + return this.value; - if (isEncryptedWithLegacyAlgorithm(this.value)) { - decryptedText = decryptUsingLegacyAlgorithm(); - } - else { - decryptedText = decryptUsingGcm(); + String textPrefix = getPrefix(this.value); + if (textPrefix == null) + throw new MendixRuntimeException("Encrypted string does not have a valid prefix."); + switch (textPrefix) { + case "AES": return decryptUsingLegacyAlgorithm(); + case "AES2": return decryptUsingGcm(); + case "AES3": return decryptUsingNewAlgorithm(); + default: + throw new MendixRuntimeException("Invalid prefix encountered when trying to decrypt string: {" + textPrefix + "}"); } - - return decryptedText; // END USER CODE } @@ -70,18 +70,54 @@ public java.lang.String toString() } // BEGIN EXTRA CODE - private final int GCM_TAG_LENGTH = 16; // in bytes - private final String LEGACY_PREFIX = "{AES}"; - private final String WRONG_KEY_ERROR_MESSAGE = "Cannot decrypt the text because it was either NOT encrypted with a key of length 16 or they key is different"; + private static final int GCM_TAG_LENGTH = 16; // in bytes + private static final String LEGACY_PREFIX = "{AES}"; + private static final String LEGACY_PREFIX2 = "{AES2}"; + private static final String NEW_PREFIX = "{AES3}"; + private static final Pattern PREFIX_REGEX = Pattern.compile("^\\{([a-zA-Z0-9]*)\\}.*$"); + private static final String WRONG_KEY_ERROR_MESSAGE = "Cannot decrypt the text because it was either NOT encrypted with a key of length 16 or the key is different"; + + private String decryptUsingNewAlgorithm() throws Exception { + if (this.key == null || this.key.isEmpty()) + throw new MendixRuntimeException("Key should not be empty"); + if (this.key.length() != 32) + throw new MendixRuntimeException("Key length should be 32"); + + String[] s = this.value.substring(NEW_PREFIX.length()).split(";"); + + if (s.length < 2) + throw new MendixRuntimeException("Unexpected prefix when trying to decrypt string."); + + Cipher c = Cipher.getInstance("AES/GCM/NoPadding"); + SecretKeySpec k = new SecretKeySpec(this.key.getBytes(), "AES"); + + byte[] iv = Base64.getDecoder().decode(s[0].getBytes()); + byte[] encryptedData = Base64.getDecoder().decode(s[1].getBytes()); + + try { + GCMParameterSpec spec = new GCMParameterSpec(GCM_TAG_LENGTH * 8, iv); + c.init(Cipher.DECRYPT_MODE, k, spec); + return new String(c.doFinal(encryptedData)); + } catch (InvalidAlgorithmParameterException | BadPaddingException ex) { + if (isEncryptedWithWrongKey(ex.getMessage())) + throw new MendixRuntimeException(WRONG_KEY_ERROR_MESSAGE); + else throw ex; + } + } private String decryptUsingGcm() throws Exception { - String[] s = this.value.substring(this.prefix.length()).split(";"); + if (this.legacyKey == null || this.legacyKey.isEmpty()) + throw new MendixRuntimeException("Legacy key should not be empty"); + if (this.legacyKey.length() != 16) + throw new MendixRuntimeException("Legacy key length should be 16"); - if (s.length < 2) //Not an encrypted string, just return the original value. - return this.value; + String[] s = this.value.substring(LEGACY_PREFIX2.length()).split(";"); + + if (s.length < 2) + throw new MendixRuntimeException("Unexpected prefix when trying to decrypt string using legacy algorithm."); Cipher c = Cipher.getInstance("AES/GCM/PKCS5PADDING"); - SecretKeySpec k = new SecretKeySpec(this.key.getBytes(), "AES"); + SecretKeySpec k = new SecretKeySpec(this.legacyKey.getBytes(), "AES"); byte[] iv = Base64.getDecoder().decode(s[0].getBytes()); byte[] encryptedData = Base64.getDecoder().decode(s[1].getBytes()); @@ -98,19 +134,23 @@ private String decryptUsingGcm() throws Exception { } private boolean isEncryptedWithWrongKey(String message) { - return - message.equals( "Wrong IV length: must be 16 bytes long") || + return message.equals("Wrong IV length: must be 16 bytes long") || message.equals("Given final block not properly padded"); } private String decryptUsingLegacyAlgorithm() throws Exception { + if (this.legacyKey == null || this.legacyKey.isEmpty()) + throw new MendixRuntimeException("Legacy key should not be empty"); + if (this.legacyKey.length() != 16) + throw new MendixRuntimeException("Legacy key length should be 16"); + String[] s = this.value.substring(LEGACY_PREFIX.length()).split(";"); - if (s.length < 2) //Not an encrypted string, just return the original value. - return this.value; + if (s.length < 2) + throw new MendixRuntimeException("Unexpected prefix when trying to decrypt string using legacy algorithm."); Cipher c = Cipher.getInstance("AES/CBC/PKCS5PADDING"); - SecretKeySpec k = new SecretKeySpec(this.key.getBytes(), "AES"); + SecretKeySpec k = new SecretKeySpec(this.legacyKey.getBytes(), "AES"); byte[] iv = Base64.getDecoder().decode(s[0].getBytes()); byte[] encryptedData = Base64.getDecoder().decode(s[1].getBytes()); @@ -125,12 +165,11 @@ private String decryptUsingLegacyAlgorithm() throws Exception { } } - private boolean isEncryptedWithLegacyAlgorithm(String text) { - return text.startsWith(LEGACY_PREFIX); - } - - private boolean isStartsWithRightPrefix() { - return this.value.startsWith(this.value) || isEncryptedWithLegacyAlgorithm(this.value); + // try to extract the prefix of an encrypted string + // returns null if no prefix is found + private String getPrefix(String text) { + Matcher m = PREFIX_REGEX.matcher(text); + return m.find() ? m.group(1) : null; } // END EXTRA CODE } diff --git a/src/EmailModuleWithTemplates/javasource/encryption/actions/EncryptString.java b/src/EmailModuleWithTemplates/javasource/encryption/actions/EncryptString.java index 7d87c70..3a0af2c 100644 --- a/src/EmailModuleWithTemplates/javasource/encryption/actions/EncryptString.java +++ b/src/EmailModuleWithTemplates/javasource/encryption/actions/EncryptString.java @@ -38,20 +38,27 @@ public java.lang.String executeAction() throws Exception return null; if (this.prefix == null || this.prefix.isEmpty()) throw new MendixRuntimeException("Prefix should not be empty"); + if(isLegacyAlgorithm(this.prefix)) + throw new MendixRuntimeException(String.format( + "The used prefix is no longer supported for encryption. Please use '%s'.", NEW_PREFIX)); + if (!hasValidPrefix(this.prefix)) + throw new MendixRuntimeException(String.format("Invalid prefix used. Please use '%s'.", NEW_PREFIX)); if (this.key == null || this.key.isEmpty()) throw new MendixRuntimeException("Key should not be empty"); - if (this.key.length() != 16) - throw new MendixRuntimeException("Key length should be 16"); - Cipher c = Cipher.getInstance("AES/GCM/PKCS5PADDING"); + if (this.key.length() != 32) + throw new MendixRuntimeException("Key length should be 32"); + Cipher c = Cipher.getInstance("AES/GCM/NoPadding"); SecretKeySpec k = new SecretKeySpec(this.key.getBytes(), "AES"); c.init(Cipher.ENCRYPT_MODE, k); byte[] encryptedData = c.doFinal(this.value.getBytes()); byte[] iv = c.getIV(); - return new StringBuilder(this.prefix + - new String(Base64.getEncoder().encode(iv))).append(";").append( - new String(Base64.getEncoder().encode(encryptedData))).toString(); + StringBuilder sb = new StringBuilder(this.prefix); + sb.append(new String(Base64.getEncoder().encode(iv))); + sb.append(';'); + sb.append(new String(Base64.getEncoder().encode(encryptedData))); + return sb.toString(); // END USER CODE } @@ -65,5 +72,10 @@ public java.lang.String toString() } // BEGIN EXTRA CODE + private static final String NEW_PREFIX = "{AES3}"; + private static final String LEGACY_PREFIX_REGEX = "^\\{AES2?}$"; + + private boolean hasValidPrefix(String text) { return text.equals(NEW_PREFIX); } + private boolean isLegacyAlgorithm(String text) { return text.matches(LEGACY_PREFIX_REGEX); } // END EXTRA CODE } diff --git a/src/EmailModuleWithTemplates/javasource/encryption/actions/PGPDecryptDocument.java b/src/EmailModuleWithTemplates/javasource/encryption/actions/PGPDecryptDocument.java index def193d..58f45e7 100644 --- a/src/EmailModuleWithTemplates/javasource/encryption/actions/PGPDecryptDocument.java +++ b/src/EmailModuleWithTemplates/javasource/encryption/actions/PGPDecryptDocument.java @@ -16,8 +16,8 @@ import encryption.proxies.microflows.Microflows; /** - * Encrypt the FileDocument using PGP encryption. - * This is allowed to be the same FileDocument instance and the action will just store th decrypted file in the entity. + * Encrypt the FileDocument using PGP encryption. + * This is allowed to be the same FileDocument instance and the action will just store the decrypted file in the entity. * * The certificate must be a File containing a valid PGP key ring (matching the document) and the certificate must have a passphrase entered in the attribute * diff --git a/src/EmailModuleWithTemplates/javasource/encryption/actions/PGPEncryptDocument.java b/src/EmailModuleWithTemplates/javasource/encryption/actions/PGPEncryptDocument.java index 1935423..4979bf7 100644 --- a/src/EmailModuleWithTemplates/javasource/encryption/actions/PGPEncryptDocument.java +++ b/src/EmailModuleWithTemplates/javasource/encryption/actions/PGPEncryptDocument.java @@ -15,11 +15,10 @@ import encryption.pgp.PGPFileProcessor; /** - * Encrypt the FileDocument using PGP encryption. - * This is allowed to be the same FileDocument instance and the action will just store the encrypted file in the provided entity. + * Encrypt the FileDocument using PGP encryption. + * This is allowed to be the same FileDocument instance and the action will just store the encrypted file in the provided entity. * - * The certificate must be a valid public PGP key provided by the external party. If you want to sign the document you can optionally provide the private key to used for signing. - * The external party would have to validate the signature against the pub key from signature key. + * The certificate must be a valid public PGP key provided by the external party. * * This action will either return true or an exception */ @@ -31,16 +30,13 @@ public class PGPEncryptDocument extends CustomJavaAction private system.proxies.FileDocument DocumentToEncrypt; private IMendixObject __OutputDocument; private system.proxies.FileDocument OutputDocument; - private IMendixObject __SigningCertificate; - private encryption.proxies.PGPCertificate SigningCertificate; - public PGPEncryptDocument(IContext context, IMendixObject ExternalPublicKey, IMendixObject DocumentToEncrypt, IMendixObject OutputDocument, IMendixObject SigningCertificate) + public PGPEncryptDocument(IContext context, IMendixObject ExternalPublicKey, IMendixObject DocumentToEncrypt, IMendixObject OutputDocument) { super(context); this.__ExternalPublicKey = ExternalPublicKey; this.__DocumentToEncrypt = DocumentToEncrypt; this.__OutputDocument = OutputDocument; - this.__SigningCertificate = SigningCertificate; } @java.lang.Override @@ -52,8 +48,6 @@ public java.lang.Boolean executeAction() throws Exception this.OutputDocument = __OutputDocument == null ? null : system.proxies.FileDocument.initialize(getContext(), __OutputDocument); - this.SigningCertificate = __SigningCertificate == null ? null : encryption.proxies.PGPCertificate.initialize(getContext(), __SigningCertificate); - // BEGIN USER CODE PGPFileProcessor p = new PGPFileProcessor(); @@ -62,9 +56,6 @@ public java.lang.Boolean executeAction() throws Exception p.setAsciiArmored(true); p.setPublicKeyFileName(this.ExternalPublicKey.getMendixObject()); - // If we are going to validate the signature we also need the private key and passphrase - // p.setPassphrase( Microflows.decrypt(getContext(), this.SigningCertificate.getPassPhrase_Encrypted()) ); - return p.encrypt(getContext()); // END USER CODE } diff --git a/src/EmailModuleWithTemplates/javasource/encryption/pgp/PGPFileProcessor.java b/src/EmailModuleWithTemplates/javasource/encryption/pgp/PGPFileProcessor.java index 513a2ab..18a7687 100644 --- a/src/EmailModuleWithTemplates/javasource/encryption/pgp/PGPFileProcessor.java +++ b/src/EmailModuleWithTemplates/javasource/encryption/pgp/PGPFileProcessor.java @@ -1,239 +1,239 @@ -package encryption.pgp; - - -import java.io.File; -import java.io.FileInputStream; -import java.io.FileOutputStream; -import java.io.IOException; -import java.io.InputStream; -import java.util.UUID; - -import org.bouncycastle.openpgp.PGPPublicKey; -import org.bouncycastle.openpgp.PGPSecretKey; - -import com.mendix.core.Core; -import com.mendix.core.CoreException; -import com.mendix.systemwideinterfaces.core.IContext; -import com.mendix.systemwideinterfaces.core.IMendixObject; - -public class PGPFileProcessor { - - private String passphrase; - private IMendixObject publicKeyFileDocument; - private IMendixObject secretKeyFileDocument; - private IMendixObject inputFileDocument; - private IMendixObject outputFileDocument; - private boolean asciiArmored = false; - private boolean integrityCheck = true; - - /** - * Encrypt the InputDocument - * - * This function requires the publicKey, OutputFile and InputFile to be specified - * - * @param context - * @return true or exception - * @throws Exception - */ - public boolean encrypt( IContext context ) throws Exception { - - if ( this.publicKeyFileDocument == null ) - throw new CoreException("Please provide a public key document"); - if ( this.outputFileDocument == null ) - throw new CoreException("Please provide an output document"); - - InputStream publicKeyIn = Core.getFileDocumentContent(context, this.publicKeyFileDocument); - - String tempOutputFile = getNewTempFile("out"); - FileOutputStream out = new FileOutputStream(tempOutputFile); - - - File tmpIn = writeInputDocumentToTempFile(context); - - PGPUtils.encryptFile(out, tmpIn.getAbsolutePath(), PGPUtils.readPublicKey(publicKeyIn), this.isAsciiArmored(), this.isIntegrityCheck()); - out.close(); - publicKeyIn.close(); - tmpIn.delete(); - - storeOutput(context, tempOutputFile); - - return true; - } - - - /** - * Encrypt and sign the InputDocument - * - * This function requires the publicKey, privateKey (for signing), OutputFile and InputFile to be specified - * - * @param context - * @return true or exception - * @throws Exception - */ - public boolean signEncrypt( IContext context ) throws Exception { - - if ( this.publicKeyFileDocument == null ) - throw new CoreException("Please provide a public key document"); - if ( this.secretKeyFileDocument == null ) - throw new CoreException("Please provide a secret key document"); - if ( this.passphrase == null ) - throw new CoreException("Please provide a pass phrase"); - if ( this.outputFileDocument == null ) - throw new CoreException("Please provide an output document"); - - - String tempOutputFile = getNewTempFile("out"); - try { - FileOutputStream out = new FileOutputStream(tempOutputFile); - - InputStream publicKeyIn = Core.getFileDocumentContent(context, this.publicKeyFileDocument); - InputStream secretKeyIn = Core.getFileDocumentContent(context, this.secretKeyFileDocument); - - PGPPublicKey publicKey = PGPUtils.readPublicKey(publicKeyIn); - PGPSecretKey secretKey = PGPUtils.readSecretKey(secretKeyIn); - - File tmpIn = writeInputDocumentToTempFile(context); - - PGPUtils.signEncryptFile( - out, - tmpIn.getAbsolutePath(), - publicKey, - secretKey, - this.getPassphrase(), - this.isAsciiArmored(), - this.isIntegrityCheck()); - - out.close(); - publicKeyIn.close(); - secretKeyIn.close(); - tmpIn.delete(); - - storeOutput(context, tempOutputFile); - } - finally { - (new File(tempOutputFile)).delete(); - } - - return true; - } - - public boolean decrypt( IContext context ) throws Exception { - InputStream in = Core.getFileDocumentContent(context, this.inputFileDocument); - InputStream keyIn = Core.getFileDocumentContent(context, this.secretKeyFileDocument); - - String tempOutputFile = getNewTempFile("out"); - try { - FileOutputStream out = new FileOutputStream(tempOutputFile); - PGPUtils.decryptFile(in, out, keyIn, this.passphrase.toCharArray()); - in.close(); - out.close(); - keyIn.close(); - - storeOutput(context, tempOutputFile); - } - finally { - (new File(tempOutputFile)).delete(); - } - - return true; - } - - - /** - * Generate a new random file name in the Mx Temp folder - * - * @param suffix - * @return full path to a file in deployment/data/tmp/ - */ - public static String getNewTempFile( String suffix ) { - return Core.getConfiguration().getTempPath().getAbsolutePath() + "/" + UUID.randomUUID().toString() + "-" + suffix + ".pgp"; - } - - /** - * Evaluate and store the content of the output file in the output file document (with the same name as input file) - * - * @param context - * @param tempOutputFile - * @throws IOException - */ - private void storeOutput( IContext context, String tempOutputFile ) throws IOException { - FileInputStream in = new FileInputStream(tempOutputFile); - Core.storeFileDocumentContent(context, this.outputFileDocument, (String) this.inputFileDocument.getValue(context, "Name"), in); - in.close(); - } - - private File writeInputDocumentToTempFile( IContext context ) throws IOException - { - File tmpFile = new File(getNewTempFile("in")); - FileOutputStream outStream = new FileOutputStream(tmpFile); - - InputStream inStream = Core.getFileDocumentContent(context, this.inputFileDocument); - - int content; - while( (content = inStream.read()) != -1 ) - { - outStream.write(content); - } - outStream.close(); - inStream.close(); - - return tmpFile; - } - - /** - * Should we use ASCII encoded output for the encryption or decryption action - * @param asciiArmored - */ - public boolean isAsciiArmored() { - return this.asciiArmored; - } - - /** - * Should we use ASCII encoded output for the encryption or decryption action - * @param asciiArmored - */ - public void setAsciiArmored( boolean asciiArmored ) { - this.asciiArmored = asciiArmored; - } - - public boolean isIntegrityCheck() { - return this.integrityCheck; - } - - public void setIntegrityCheck( boolean integrityCheck ) { - this.integrityCheck = integrityCheck; - } - - public String getPassphrase() { - return this.passphrase; - } - - public void setPassphrase( String passphrase ) { - this.passphrase = passphrase; - } - - public IMendixObject getPublicKeyFileName() { - return this.publicKeyFileDocument; - } - - public void setPublicKeyFileName( IMendixObject publicKeyFileDocument ) { - this.publicKeyFileDocument = publicKeyFileDocument; - } - - public IMendixObject getSecretKeyFileName() { - return this.secretKeyFileDocument; - } - - public void setSecretKeyFileName( IMendixObject secretKeyFileDocument ) { - this.secretKeyFileDocument = secretKeyFileDocument; - } - - - public void setInputFileDocument( IMendixObject inputFileDocument ) { - this.inputFileDocument = inputFileDocument; - } - - public void setOutputFileDocument( IMendixObject outputFileDocument ) { - this.outputFileDocument = outputFileDocument; - } +package encryption.pgp; + + +import java.io.File; +import java.io.FileInputStream; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.util.UUID; + +import org.bouncycastle.openpgp.PGPPublicKey; +import org.bouncycastle.openpgp.PGPSecretKey; + +import com.mendix.core.Core; +import com.mendix.core.CoreException; +import com.mendix.systemwideinterfaces.core.IContext; +import com.mendix.systemwideinterfaces.core.IMendixObject; + +public class PGPFileProcessor { + + private String passphrase; + private IMendixObject publicKeyFileDocument; + private IMendixObject secretKeyFileDocument; + private IMendixObject inputFileDocument; + private IMendixObject outputFileDocument; + private boolean asciiArmored = false; + private boolean integrityCheck = true; + + /** + * Encrypt the InputDocument + * + * This function requires the publicKey, OutputFile and InputFile to be specified + * + * @param context + * @return true or exception + * @throws Exception + */ + public boolean encrypt( IContext context ) throws Exception { + + if ( this.publicKeyFileDocument == null ) + throw new CoreException("Please provide a public key document"); + if ( this.outputFileDocument == null ) + throw new CoreException("Please provide an output document"); + + InputStream publicKeyIn = Core.getFileDocumentContent(context, this.publicKeyFileDocument); + + String tempOutputFile = getNewTempFile("out"); + FileOutputStream out = new FileOutputStream(tempOutputFile); + + + File tmpIn = writeInputDocumentToTempFile(context); + + PGPUtils.encryptFile(out, tmpIn.getAbsolutePath(), PGPUtils.readPublicKey(publicKeyIn), this.isAsciiArmored(), this.isIntegrityCheck()); + out.close(); + publicKeyIn.close(); + tmpIn.delete(); + + storeOutput(context, tempOutputFile); + + return true; + } + + + /** + * Encrypt and sign the InputDocument + * + * This function requires the publicKey, privateKey (for signing), OutputFile and InputFile to be specified + * + * @param context + * @return true or exception + * @throws Exception + */ + public boolean signEncrypt( IContext context ) throws Exception { + + if ( this.publicKeyFileDocument == null ) + throw new CoreException("Please provide a public key document"); + if ( this.secretKeyFileDocument == null ) + throw new CoreException("Please provide a secret key document"); + if ( this.passphrase == null ) + throw new CoreException("Please provide a pass phrase"); + if ( this.outputFileDocument == null ) + throw new CoreException("Please provide an output document"); + + + String tempOutputFile = getNewTempFile("out"); + try { + FileOutputStream out = new FileOutputStream(tempOutputFile); + + InputStream publicKeyIn = Core.getFileDocumentContent(context, this.publicKeyFileDocument); + InputStream secretKeyIn = Core.getFileDocumentContent(context, this.secretKeyFileDocument); + + PGPPublicKey publicKey = PGPUtils.readPublicKey(publicKeyIn); + PGPSecretKey secretKey = PGPUtils.readSecretKey(secretKeyIn); + + File tmpIn = writeInputDocumentToTempFile(context); + + PGPUtils.signEncryptFile( + out, + tmpIn.getAbsolutePath(), + publicKey, + secretKey, + this.getPassphrase(), + this.isAsciiArmored(), + this.isIntegrityCheck()); + + out.close(); + publicKeyIn.close(); + secretKeyIn.close(); + tmpIn.delete(); + + storeOutput(context, tempOutputFile); + } + finally { + (new File(tempOutputFile)).delete(); + } + + return true; + } + + public boolean decrypt( IContext context ) throws Exception { + InputStream in = Core.getFileDocumentContent(context, this.inputFileDocument); + InputStream keyIn = Core.getFileDocumentContent(context, this.secretKeyFileDocument); + + String tempOutputFile = getNewTempFile("out"); + try { + FileOutputStream out = new FileOutputStream(tempOutputFile); + PGPUtils.decryptFile(in, out, keyIn, this.passphrase.toCharArray()); + in.close(); + out.close(); + keyIn.close(); + + storeOutput(context, tempOutputFile); + } + finally { + (new File(tempOutputFile)).delete(); + } + + return true; + } + + + /** + * Generate a new random file name in the Mx Temp folder + * + * @param suffix + * @return full path to a file in deployment/data/tmp/ + */ + public static String getNewTempFile( String suffix ) { + return Core.getConfiguration().getTempPath().getAbsolutePath() + "/" + UUID.randomUUID().toString() + "-" + suffix + ".pgp"; + } + + /** + * Evaluate and store the content of the output file in the output file document (with the same name as input file) + * + * @param context + * @param tempOutputFile + * @throws IOException + */ + private void storeOutput( IContext context, String tempOutputFile ) throws IOException { + FileInputStream in = new FileInputStream(tempOutputFile); + Core.storeFileDocumentContent(context, this.outputFileDocument, (String) this.inputFileDocument.getValue(context, "Name"), in); + in.close(); + } + + private File writeInputDocumentToTempFile( IContext context ) throws IOException + { + File tmpFile = new File(getNewTempFile("in")); + FileOutputStream outStream = new FileOutputStream(tmpFile); + + InputStream inStream = Core.getFileDocumentContent(context, this.inputFileDocument); + + int content; + while( (content = inStream.read()) != -1 ) + { + outStream.write(content); + } + outStream.close(); + inStream.close(); + + return tmpFile; + } + + /** + * Should we use ASCII encoded output for the encryption or decryption action + * @param asciiArmored + */ + public boolean isAsciiArmored() { + return this.asciiArmored; + } + + /** + * Should we use ASCII encoded output for the encryption or decryption action + * @param asciiArmored + */ + public void setAsciiArmored( boolean asciiArmored ) { + this.asciiArmored = asciiArmored; + } + + public boolean isIntegrityCheck() { + return this.integrityCheck; + } + + public void setIntegrityCheck( boolean integrityCheck ) { + this.integrityCheck = integrityCheck; + } + + public String getPassphrase() { + return this.passphrase; + } + + public void setPassphrase( String passphrase ) { + this.passphrase = passphrase; + } + + public IMendixObject getPublicKeyFileName() { + return this.publicKeyFileDocument; + } + + public void setPublicKeyFileName( IMendixObject publicKeyFileDocument ) { + this.publicKeyFileDocument = publicKeyFileDocument; + } + + public IMendixObject getSecretKeyFileName() { + return this.secretKeyFileDocument; + } + + public void setSecretKeyFileName( IMendixObject secretKeyFileDocument ) { + this.secretKeyFileDocument = secretKeyFileDocument; + } + + + public void setInputFileDocument( IMendixObject inputFileDocument ) { + this.inputFileDocument = inputFileDocument; + } + + public void setOutputFileDocument( IMendixObject outputFileDocument ) { + this.outputFileDocument = outputFileDocument; + } } \ No newline at end of file diff --git a/src/EmailModuleWithTemplates/javasource/encryption/pgp/PGPUtils.java b/src/EmailModuleWithTemplates/javasource/encryption/pgp/PGPUtils.java index 1f132e8..21a2451 100644 --- a/src/EmailModuleWithTemplates/javasource/encryption/pgp/PGPUtils.java +++ b/src/EmailModuleWithTemplates/javasource/encryption/pgp/PGPUtils.java @@ -1,518 +1,518 @@ -package encryption.pgp; - -import java.io.ByteArrayOutputStream; -import java.io.File; -import java.io.FileInputStream; -import java.io.FileOutputStream; -import java.io.IOException; -import java.io.InputStream; -import java.io.OutputStream; -import java.security.NoSuchProviderException; -import java.security.Provider; -import java.security.SecureRandom; -import java.security.Security; -import java.util.Date; -import java.util.Iterator; - -import org.apache.commons.io.IOUtils; -import org.bouncycastle.bcpg.ArmoredOutputStream; -import org.bouncycastle.bcpg.HashAlgorithmTags; -import org.bouncycastle.bcpg.PublicKeyAlgorithmTags; -import org.bouncycastle.bcpg.sig.KeyFlags; -import org.bouncycastle.jce.provider.BouncyCastleProvider; -import org.bouncycastle.openpgp.PGPCompressedData; -import org.bouncycastle.openpgp.PGPCompressedDataGenerator; -import org.bouncycastle.openpgp.PGPEncryptedData; -import org.bouncycastle.openpgp.PGPEncryptedDataGenerator; -import org.bouncycastle.openpgp.PGPEncryptedDataList; -import org.bouncycastle.openpgp.PGPException; -import org.bouncycastle.openpgp.PGPLiteralData; -import org.bouncycastle.openpgp.PGPLiteralDataGenerator; -import org.bouncycastle.openpgp.PGPObjectFactory; -import org.bouncycastle.openpgp.PGPOnePassSignature; -import org.bouncycastle.openpgp.PGPOnePassSignatureList; -import org.bouncycastle.openpgp.PGPPrivateKey; -import org.bouncycastle.openpgp.PGPPublicKey; -import org.bouncycastle.openpgp.PGPPublicKeyEncryptedData; -import org.bouncycastle.openpgp.PGPPublicKeyRing; -import org.bouncycastle.openpgp.PGPPublicKeyRingCollection; -import org.bouncycastle.openpgp.PGPSecretKey; -import org.bouncycastle.openpgp.PGPSecretKeyRing; -import org.bouncycastle.openpgp.PGPSecretKeyRingCollection; -import org.bouncycastle.openpgp.PGPSignature; -import org.bouncycastle.openpgp.PGPSignatureGenerator; -import org.bouncycastle.openpgp.PGPSignatureList; -import org.bouncycastle.openpgp.PGPSignatureSubpacketGenerator; -import org.bouncycastle.openpgp.PGPSignatureSubpacketVector; -import org.bouncycastle.openpgp.PGPUtil; -import org.bouncycastle.openpgp.operator.PBESecretKeyDecryptor; -import org.bouncycastle.openpgp.operator.PGPContentSignerBuilder; -import org.bouncycastle.openpgp.operator.bc.BcPBESecretKeyDecryptorBuilder; -import org.bouncycastle.openpgp.operator.bc.BcPGPContentSignerBuilder; -import org.bouncycastle.openpgp.operator.bc.BcPGPContentVerifierBuilderProvider; -import org.bouncycastle.openpgp.operator.bc.BcPGPDataEncryptorBuilder; -import org.bouncycastle.openpgp.operator.bc.BcPGPDigestCalculatorProvider; -import org.bouncycastle.openpgp.operator.bc.BcPublicKeyDataDecryptorFactory; -import org.bouncycastle.openpgp.operator.bc.BcPublicKeyKeyEncryptionMethodGenerator; -import org.bouncycastle.openpgp.operator.bc.BcKeyFingerprintCalculator; - -public class PGPUtils { - - private static final int BUFFER_SIZE = 1 << 16; // should always be power of 2 - private static final int KEY_FLAGS = 27; - private static final int[] MASTER_KEY_CERTIFICATION_TYPES = new int[] { - PGPSignature.POSITIVE_CERTIFICATION, - PGPSignature.CASUAL_CERTIFICATION, - PGPSignature.NO_CERTIFICATION, - PGPSignature.DEFAULT_CERTIFICATION - }; - - @SuppressWarnings("unchecked") - public static PGPPublicKey readPublicKey( InputStream in ) - throws IOException, PGPException - { - - PGPPublicKeyRingCollection keyRingCollection = new PGPPublicKeyRingCollection(PGPUtil.getDecoderStream(in), new BcKeyFingerprintCalculator()); - - // - // we just loop through the collection till we find a key suitable for encryption, in the real - // world you would probably want to be a bit smarter about this. - // - PGPPublicKey publicKey = null; - - // - // iterate through the key rings. - // - Iterator rIt = keyRingCollection.getKeyRings(); - - while( publicKey == null && rIt.hasNext() ) { - PGPPublicKeyRing kRing = rIt.next(); - Iterator kIt = kRing.getPublicKeys(); - while( publicKey == null && kIt.hasNext() ) { - PGPPublicKey key = kIt.next(); - if ( key.isEncryptionKey() ) { - publicKey = key; - } - } - } - - if ( publicKey == null ) { - throw new IllegalArgumentException("Can't find public key in the key ring."); - } - if ( !isForEncryption(publicKey) ) { - throw new IllegalArgumentException("KeyID " + publicKey.getKeyID() + " not flagged for encryption."); - } - - return publicKey; - } - - @SuppressWarnings("unchecked") - public static PGPSecretKey readSecretKey( InputStream in ) - throws IOException, PGPException - { - - PGPSecretKeyRingCollection keyRingCollection = new PGPSecretKeyRingCollection(PGPUtil.getDecoderStream(in), new BcKeyFingerprintCalculator()); - - // - // We just loop through the collection till we find a key suitable for signing. - // In the real world you would probably want to be a bit smarter about this. - // - PGPSecretKey secretKey = null; - - Iterator rIt = keyRingCollection.getKeyRings(); - while( secretKey == null && rIt.hasNext() ) { - PGPSecretKeyRing keyRing = rIt.next(); - Iterator kIt = keyRing.getSecretKeys(); - while( secretKey == null && kIt.hasNext() ) { - PGPSecretKey key = kIt.next(); - if ( key.isSigningKey() ) { - secretKey = key; - } - } - } - - // Validate secret key - if ( secretKey == null ) { - throw new IllegalArgumentException("Can't find private key in the key ring."); - } - if ( !secretKey.isSigningKey() ) { - throw new IllegalArgumentException("Private key does not allow signing."); - } - if ( secretKey.getPublicKey().isRevoked() ) { - throw new IllegalArgumentException("Private key has been revoked."); - } - if ( !hasKeyFlags(secretKey.getPublicKey(), KeyFlags.SIGN_DATA) ) { - throw new IllegalArgumentException("Key cannot be used for signing."); - } - - return secretKey; - } - - /** - * Load a secret key ring collection from keyIn and find the private key corresponding to - * keyID if it exists. - * - * @param keyIn input stream representing a key ring collection. - * @param keyID keyID we want. - * @param pass passphrase to decrypt secret key with. - * @return - * @throws IOException - * @throws PGPException - * @throws NoSuchProviderException - */ - public static PGPPrivateKey findPrivateKey( InputStream keyIn, long keyID, char[] pass ) - throws IOException, PGPException, NoSuchProviderException - { - PGPSecretKeyRingCollection pgpSec = new PGPSecretKeyRingCollection(PGPUtil.getDecoderStream(keyIn), new BcKeyFingerprintCalculator()); - return findPrivateKey(pgpSec.getSecretKey(keyID), pass); - - } - - /** - * Load a secret key and find the private key in it - * - * @param pgpSecKey The secret key - * @param pass passphrase to decrypt secret key with - * @return - * @throws PGPException - */ - public static PGPPrivateKey findPrivateKey( PGPSecretKey pgpSecKey, char[] pass ) - throws PGPException - { - if ( pgpSecKey == null ) - return null; - - PBESecretKeyDecryptor decryptor = new BcPBESecretKeyDecryptorBuilder(new BcPGPDigestCalculatorProvider()).build(pass); - return pgpSecKey.extractPrivateKey(decryptor); - } - - /** - * decrypt the passed in message stream - */ - @SuppressWarnings("unchecked") - public static void decryptFile( InputStream in, OutputStream out, InputStream keyIn, char[] passwd ) - throws Exception - { - Security.addProvider(new BouncyCastleProvider()); - - in = org.bouncycastle.openpgp.PGPUtil.getDecoderStream(in); - - PGPObjectFactory pgpF = new PGPObjectFactory(in, new BcKeyFingerprintCalculator()); - PGPEncryptedDataList enc; - - Object o = pgpF.nextObject(); - // - // the first object might be a PGP marker packet. - // - if ( o instanceof PGPEncryptedDataList ) { - enc = (PGPEncryptedDataList) o; - } - else { - enc = (PGPEncryptedDataList) pgpF.nextObject(); - } - - // - // find the secret key - // - Iterator it = enc.getEncryptedDataObjects(); - PGPPrivateKey sKey = null; - PGPPublicKeyEncryptedData pbe = null; - - while( sKey == null && it.hasNext() ) { - PGPEncryptedData data = it.next(); - if (data instanceof PGPPublicKeyEncryptedData) { - pbe = (PGPPublicKeyEncryptedData) data; - - sKey = findPrivateKey(keyIn, pbe.getKeyID(), passwd); - } - } - - if ( sKey == null ) { - throw new IllegalArgumentException("Secret key for message not found. (Looking for key:" + pbe.getKeyID() + ")"); - } - - InputStream clear = pbe.getDataStream(new BcPublicKeyDataDecryptorFactory(sKey)); - - PGPObjectFactory plainFact = new PGPObjectFactory(clear, new BcKeyFingerprintCalculator()); - - Object message = plainFact.nextObject(); - - if ( message instanceof PGPCompressedData ) { - PGPCompressedData cData = (PGPCompressedData) message; - PGPObjectFactory pgpFact = new PGPObjectFactory(cData.getDataStream(), new BcKeyFingerprintCalculator()); - - message = pgpFact.nextObject(); - } - - if ( message instanceof PGPLiteralData ) { - PGPLiteralData ld = (PGPLiteralData) message; - - InputStream unc = ld.getInputStream(); - int ch; - - while( (ch = unc.read()) >= 0 ) { - out.write(ch); - } - } - else if ( message instanceof PGPOnePassSignatureList ) { - throw new PGPException("Encrypted message contains a signed message - not literal data."); - } - else { - throw new PGPException("Message is not a simple encrypted file - type unknown."); - } - - if ( pbe.isIntegrityProtected() ) { - if ( !pbe.verify() ) { - throw new PGPException("Message failed integrity check"); - } - } - } - - public static void encryptFile( - OutputStream out, - String fileName, - PGPPublicKey encKey, - boolean armor, - boolean withIntegrityCheck ) - throws IOException, NoSuchProviderException, PGPException - { - Security.addProvider(new BouncyCastleProvider()); - - if ( armor ) { - out = new ArmoredOutputStream(out); - } - - ByteArrayOutputStream bOut = new ByteArrayOutputStream(); - PGPCompressedDataGenerator comData = new PGPCompressedDataGenerator(PGPCompressedData.ZIP); - - PGPUtil.writeFileToLiteralData( - comData.open(bOut), - PGPLiteralData.BINARY, - new File(fileName)); - - comData.close(); - - BcPGPDataEncryptorBuilder dataEncryptor = new BcPGPDataEncryptorBuilder(PGPEncryptedData.TRIPLE_DES); - dataEncryptor.setWithIntegrityPacket(withIntegrityCheck); - dataEncryptor.setSecureRandom(new SecureRandom()); - - PGPEncryptedDataGenerator encryptedDataGenerator = new PGPEncryptedDataGenerator(dataEncryptor); - encryptedDataGenerator.addMethod(new BcPublicKeyKeyEncryptionMethodGenerator(encKey)); - - byte[] bytes = bOut.toByteArray(); - OutputStream cOut = encryptedDataGenerator.open(out, bytes.length); - cOut.write(bytes); - cOut.close(); - - encryptedDataGenerator.close(); - bOut.close(); - - - if( armor ) { - out.close(); - } - } - - @SuppressWarnings("unchecked") - public static void signEncryptFile( - OutputStream out, - String fileName, - PGPPublicKey publicKey, - PGPSecretKey secretKey, - String password, - boolean armor, - boolean withIntegrityCheck ) - throws Exception - { - - // Initialize Bouncy Castle security provider - Provider provider = new BouncyCastleProvider(); - Security.addProvider(provider); - - if ( armor ) { - out = new ArmoredOutputStream(out); - } - - BcPGPDataEncryptorBuilder dataEncryptor = new BcPGPDataEncryptorBuilder(PGPEncryptedData.TRIPLE_DES); - dataEncryptor.setWithIntegrityPacket(withIntegrityCheck); - dataEncryptor.setSecureRandom(new SecureRandom()); - - PGPEncryptedDataGenerator encryptedDataGenerator = new PGPEncryptedDataGenerator(dataEncryptor); - encryptedDataGenerator.addMethod(new BcPublicKeyKeyEncryptionMethodGenerator(publicKey)); - - OutputStream encryptedOut = encryptedDataGenerator.open(out, new byte[PGPUtils.BUFFER_SIZE]); - - // Initialize compressed data generator - PGPCompressedDataGenerator compressedDataGenerator = new PGPCompressedDataGenerator(PGPCompressedData.ZIP); - OutputStream compressedOut = compressedDataGenerator.open(encryptedOut, new byte[PGPUtils.BUFFER_SIZE]); - - // Initialize signature generator - PGPPrivateKey privateKey = findPrivateKey(secretKey, password.toCharArray()); - - PGPContentSignerBuilder signerBuilder = new BcPGPContentSignerBuilder(secretKey.getPublicKey().getAlgorithm(), - HashAlgorithmTags.SHA1); - - PGPSignatureGenerator signatureGenerator = new PGPSignatureGenerator(signerBuilder); - signatureGenerator.init(PGPSignature.BINARY_DOCUMENT, privateKey); - - boolean firstTime = true; - Iterator it = secretKey.getPublicKey().getUserIDs(); - while( it.hasNext() && firstTime ) { - PGPSignatureSubpacketGenerator spGen = new PGPSignatureSubpacketGenerator(); - spGen.setSignerUserID(false, it.next()); - signatureGenerator.setHashedSubpackets(spGen.generate()); - // Exit the loop after the first iteration - firstTime = false; - } - signatureGenerator.generateOnePassVersion(false).encode(compressedOut); - - // Initialize literal data generator - PGPLiteralDataGenerator literalDataGenerator = new PGPLiteralDataGenerator(); - OutputStream literalOut = literalDataGenerator.open( - compressedOut, - PGPLiteralData.BINARY, - fileName, - new Date(), - new byte[PGPUtils.BUFFER_SIZE]); - - // Main loop - read the "in" stream, compress, encrypt and write to the "out" stream - FileInputStream in = new FileInputStream(fileName); - byte[] buf = new byte[PGPUtils.BUFFER_SIZE]; - int len; - while( (len = in.read(buf)) > 0 ) { - literalOut.write(buf, 0, len); - signatureGenerator.update(buf, 0, len); - } - - literalOut.close(); - in.close(); - literalDataGenerator.close(); - // Generate the signature, compress, encrypt and write to the "out" stream - signatureGenerator.generate().encode(compressedOut); - compressedDataGenerator.close(); - encryptedDataGenerator.close(); - if ( armor ) { - out.close(); - } - } - - public static boolean verifyFile( - InputStream in, - InputStream keyIn, - String extractContentFile ) - throws Exception - { - in = PGPUtil.getDecoderStream(in); - - PGPObjectFactory pgpFact = new PGPObjectFactory(in, new BcKeyFingerprintCalculator()); - PGPCompressedData c1 = (PGPCompressedData) pgpFact.nextObject(); - - pgpFact = new PGPObjectFactory(c1.getDataStream(), new BcKeyFingerprintCalculator()); - - PGPOnePassSignatureList p1 = (PGPOnePassSignatureList) pgpFact.nextObject(); - - PGPOnePassSignature ops = p1.get(0); - - PGPLiteralData p2 = (PGPLiteralData) pgpFact.nextObject(); - - InputStream dIn = p2.getInputStream(); - - IOUtils.copy(dIn, new FileOutputStream(extractContentFile)); - - int ch; - PGPPublicKeyRingCollection pgpRing = new PGPPublicKeyRingCollection(PGPUtil.getDecoderStream(keyIn), new BcKeyFingerprintCalculator()); - - PGPPublicKey key = pgpRing.getPublicKey(ops.getKeyID()); - - FileOutputStream out = new FileOutputStream(p2.getFileName()); - - ops.init(new BcPGPContentVerifierBuilderProvider(), key); - - while( (ch = dIn.read()) >= 0 ) - { - ops.update((byte) ch); - out.write(ch); - } - - out.close(); - - PGPSignatureList p3 = (PGPSignatureList) pgpFact.nextObject(); - return ops.verify(p3.get(0)); - } - - /** - * From LockBox Lobs PGP Encryption tools. - * http://www.lockboxlabs.org/content/downloads - * - * I didn't think it was worth having to import a 4meg lib for three methods - * - * @param key - * @return - */ - public static boolean isForEncryption( PGPPublicKey key ) - { - if ( key.getAlgorithm() == PublicKeyAlgorithmTags.RSA_SIGN - || key.getAlgorithm() == PublicKeyAlgorithmTags.DSA - || key.getAlgorithm() == PublicKeyAlgorithmTags.EC - || key.getAlgorithm() == PublicKeyAlgorithmTags.ECDSA ) - { - return false; - } - - return hasKeyFlags(key, KeyFlags.ENCRYPT_COMMS | KeyFlags.ENCRYPT_STORAGE); - } - - /** - * From LockBox Lobs PGP Encryption tools. - * http://www.lockboxlabs.org/content/downloads - * - * I didn't think it was worth having to import a 4meg lib for three methods - * - * @param key - * @return - */ - @SuppressWarnings("unchecked") - private static boolean hasKeyFlags( PGPPublicKey encKey, int keyUsage ) { - if ( encKey.isMasterKey() ) { - for( int i = 0; i != PGPUtils.MASTER_KEY_CERTIFICATION_TYPES.length; i++ ) { - for( Iterator eIt = encKey.getSignaturesOfType(PGPUtils.MASTER_KEY_CERTIFICATION_TYPES[i]); eIt.hasNext(); ) { - PGPSignature sig = eIt.next(); - if ( !isMatchingUsage(sig, keyUsage) ) { - return false; - } - } - } - } - else { - for( Iterator eIt = encKey.getSignaturesOfType(PGPSignature.SUBKEY_BINDING); eIt.hasNext(); ) { - PGPSignature sig = eIt.next(); - if ( !isMatchingUsage(sig, keyUsage) ) { - return false; - } - } - } - return true; - } - - /** - * From LockBox Lobs PGP Encryption tools. - * http://www.lockboxlabs.org/content/downloads - * - * I didn't think it was worth having to import a 4meg lib for three methods - * - * @param key - * @return - */ - private static boolean isMatchingUsage( PGPSignature sig, int keyUsage ) { - if ( sig.hasSubpackets() ) { - PGPSignatureSubpacketVector sv = sig.getHashedSubPackets(); - if ( sv.hasSubpacket(PGPUtils.KEY_FLAGS) ) { - if ( (sv.getKeyFlags() == 0 && keyUsage == 0) ) { - return false; - } - } - } - return true; - } - +package encryption.pgp; + +import java.io.ByteArrayOutputStream; +import java.io.File; +import java.io.FileInputStream; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.security.NoSuchProviderException; +import java.security.Provider; +import java.security.SecureRandom; +import java.security.Security; +import java.util.Date; +import java.util.Iterator; + +import org.apache.commons.io.IOUtils; +import org.bouncycastle.bcpg.ArmoredOutputStream; +import org.bouncycastle.bcpg.HashAlgorithmTags; +import org.bouncycastle.bcpg.PublicKeyAlgorithmTags; +import org.bouncycastle.bcpg.sig.KeyFlags; +import org.bouncycastle.jce.provider.BouncyCastleProvider; +import org.bouncycastle.openpgp.PGPCompressedData; +import org.bouncycastle.openpgp.PGPCompressedDataGenerator; +import org.bouncycastle.openpgp.PGPEncryptedData; +import org.bouncycastle.openpgp.PGPEncryptedDataGenerator; +import org.bouncycastle.openpgp.PGPEncryptedDataList; +import org.bouncycastle.openpgp.PGPException; +import org.bouncycastle.openpgp.PGPLiteralData; +import org.bouncycastle.openpgp.PGPLiteralDataGenerator; +import org.bouncycastle.openpgp.PGPObjectFactory; +import org.bouncycastle.openpgp.PGPOnePassSignature; +import org.bouncycastle.openpgp.PGPOnePassSignatureList; +import org.bouncycastle.openpgp.PGPPrivateKey; +import org.bouncycastle.openpgp.PGPPublicKey; +import org.bouncycastle.openpgp.PGPPublicKeyEncryptedData; +import org.bouncycastle.openpgp.PGPPublicKeyRing; +import org.bouncycastle.openpgp.PGPPublicKeyRingCollection; +import org.bouncycastle.openpgp.PGPSecretKey; +import org.bouncycastle.openpgp.PGPSecretKeyRing; +import org.bouncycastle.openpgp.PGPSecretKeyRingCollection; +import org.bouncycastle.openpgp.PGPSignature; +import org.bouncycastle.openpgp.PGPSignatureGenerator; +import org.bouncycastle.openpgp.PGPSignatureList; +import org.bouncycastle.openpgp.PGPSignatureSubpacketGenerator; +import org.bouncycastle.openpgp.PGPSignatureSubpacketVector; +import org.bouncycastle.openpgp.PGPUtil; +import org.bouncycastle.openpgp.operator.PBESecretKeyDecryptor; +import org.bouncycastle.openpgp.operator.PGPContentSignerBuilder; +import org.bouncycastle.openpgp.operator.bc.BcPBESecretKeyDecryptorBuilder; +import org.bouncycastle.openpgp.operator.bc.BcPGPContentSignerBuilder; +import org.bouncycastle.openpgp.operator.bc.BcPGPContentVerifierBuilderProvider; +import org.bouncycastle.openpgp.operator.bc.BcPGPDataEncryptorBuilder; +import org.bouncycastle.openpgp.operator.bc.BcPGPDigestCalculatorProvider; +import org.bouncycastle.openpgp.operator.bc.BcPublicKeyDataDecryptorFactory; +import org.bouncycastle.openpgp.operator.bc.BcPublicKeyKeyEncryptionMethodGenerator; +import org.bouncycastle.openpgp.operator.bc.BcKeyFingerprintCalculator; + +public class PGPUtils { + + private static final int BUFFER_SIZE = 1 << 16; // should always be power of 2 + private static final int KEY_FLAGS = 27; + private static final int[] MASTER_KEY_CERTIFICATION_TYPES = new int[] { + PGPSignature.POSITIVE_CERTIFICATION, + PGPSignature.CASUAL_CERTIFICATION, + PGPSignature.NO_CERTIFICATION, + PGPSignature.DEFAULT_CERTIFICATION + }; + + @SuppressWarnings("unchecked") + public static PGPPublicKey readPublicKey( InputStream in ) + throws IOException, PGPException + { + + PGPPublicKeyRingCollection keyRingCollection = new PGPPublicKeyRingCollection(PGPUtil.getDecoderStream(in), new BcKeyFingerprintCalculator()); + + // + // we just loop through the collection till we find a key suitable for encryption, in the real + // world you would probably want to be a bit smarter about this. + // + PGPPublicKey publicKey = null; + + // + // iterate through the key rings. + // + Iterator rIt = keyRingCollection.getKeyRings(); + + while( publicKey == null && rIt.hasNext() ) { + PGPPublicKeyRing kRing = rIt.next(); + Iterator kIt = kRing.getPublicKeys(); + while( publicKey == null && kIt.hasNext() ) { + PGPPublicKey key = kIt.next(); + if ( key.isEncryptionKey() ) { + publicKey = key; + } + } + } + + if ( publicKey == null ) { + throw new IllegalArgumentException("Can't find public key in the key ring."); + } + if ( !isForEncryption(publicKey) ) { + throw new IllegalArgumentException("KeyID " + publicKey.getKeyID() + " not flagged for encryption."); + } + + return publicKey; + } + + @SuppressWarnings("unchecked") + public static PGPSecretKey readSecretKey( InputStream in ) + throws IOException, PGPException + { + + PGPSecretKeyRingCollection keyRingCollection = new PGPSecretKeyRingCollection(PGPUtil.getDecoderStream(in), new BcKeyFingerprintCalculator()); + + // + // We just loop through the collection till we find a key suitable for signing. + // In the real world you would probably want to be a bit smarter about this. + // + PGPSecretKey secretKey = null; + + Iterator rIt = keyRingCollection.getKeyRings(); + while( secretKey == null && rIt.hasNext() ) { + PGPSecretKeyRing keyRing = rIt.next(); + Iterator kIt = keyRing.getSecretKeys(); + while( secretKey == null && kIt.hasNext() ) { + PGPSecretKey key = kIt.next(); + if ( key.isSigningKey() ) { + secretKey = key; + } + } + } + + // Validate secret key + if ( secretKey == null ) { + throw new IllegalArgumentException("Can't find private key in the key ring."); + } + if ( !secretKey.isSigningKey() ) { + throw new IllegalArgumentException("Private key does not allow signing."); + } + if ( secretKey.getPublicKey().hasRevocation() ) { + throw new IllegalArgumentException("Private key has been revoked."); + } + if ( !hasKeyFlags(secretKey.getPublicKey(), KeyFlags.SIGN_DATA) ) { + throw new IllegalArgumentException("Key cannot be used for signing."); + } + + return secretKey; + } + + /** + * Load a secret key ring collection from keyIn and find the private key corresponding to + * keyID if it exists. + * + * @param keyIn input stream representing a key ring collection. + * @param keyID keyID we want. + * @param pass passphrase to decrypt secret key with. + * @return + * @throws IOException + * @throws PGPException + * @throws NoSuchProviderException + */ + public static PGPPrivateKey findPrivateKey( InputStream keyIn, long keyID, char[] pass ) + throws IOException, PGPException, NoSuchProviderException + { + PGPSecretKeyRingCollection pgpSec = new PGPSecretKeyRingCollection(PGPUtil.getDecoderStream(keyIn), new BcKeyFingerprintCalculator()); + return findPrivateKey(pgpSec.getSecretKey(keyID), pass); + + } + + /** + * Load a secret key and find the private key in it + * + * @param pgpSecKey The secret key + * @param pass passphrase to decrypt secret key with + * @return + * @throws PGPException + */ + public static PGPPrivateKey findPrivateKey( PGPSecretKey pgpSecKey, char[] pass ) + throws PGPException + { + if ( pgpSecKey == null ) + return null; + + PBESecretKeyDecryptor decryptor = new BcPBESecretKeyDecryptorBuilder(new BcPGPDigestCalculatorProvider()).build(pass); + return pgpSecKey.extractPrivateKey(decryptor); + } + + /** + * decrypt the passed in message stream + */ + @SuppressWarnings("unchecked") + public static void decryptFile( InputStream in, OutputStream out, InputStream keyIn, char[] passwd ) + throws Exception + { + Security.addProvider(new BouncyCastleProvider()); + + in = org.bouncycastle.openpgp.PGPUtil.getDecoderStream(in); + + PGPObjectFactory pgpF = new PGPObjectFactory(in, new BcKeyFingerprintCalculator()); + PGPEncryptedDataList enc; + + Object o = pgpF.nextObject(); + // + // the first object might be a PGP marker packet. + // + if ( o instanceof PGPEncryptedDataList ) { + enc = (PGPEncryptedDataList) o; + } + else { + enc = (PGPEncryptedDataList) pgpF.nextObject(); + } + + // + // find the secret key + // + Iterator it = enc.getEncryptedDataObjects(); + PGPPrivateKey sKey = null; + PGPPublicKeyEncryptedData pbe = null; + + while( sKey == null && it.hasNext() ) { + PGPEncryptedData data = it.next(); + if (data instanceof PGPPublicKeyEncryptedData) { + pbe = (PGPPublicKeyEncryptedData) data; + + sKey = findPrivateKey(keyIn, pbe.getKeyID(), passwd); + } + } + + if ( sKey == null ) { + throw new IllegalArgumentException("Secret key for message not found. (Looking for key:" + pbe.getKeyID() + ")"); + } + + InputStream clear = pbe.getDataStream(new BcPublicKeyDataDecryptorFactory(sKey)); + + PGPObjectFactory plainFact = new PGPObjectFactory(clear, new BcKeyFingerprintCalculator()); + + Object message = plainFact.nextObject(); + + if ( message instanceof PGPCompressedData ) { + PGPCompressedData cData = (PGPCompressedData) message; + PGPObjectFactory pgpFact = new PGPObjectFactory(cData.getDataStream(), new BcKeyFingerprintCalculator()); + + message = pgpFact.nextObject(); + } + + if ( message instanceof PGPLiteralData ) { + PGPLiteralData ld = (PGPLiteralData) message; + + InputStream unc = ld.getInputStream(); + int ch; + + while( (ch = unc.read()) >= 0 ) { + out.write(ch); + } + } + else if ( message instanceof PGPOnePassSignatureList ) { + throw new PGPException("Encrypted message contains a signed message - not literal data."); + } + else { + throw new PGPException("Message is not a simple encrypted file - type unknown."); + } + + if ( pbe.isIntegrityProtected() ) { + if ( !pbe.verify() ) { + throw new PGPException("Message failed integrity check"); + } + } + } + + public static void encryptFile( + OutputStream out, + String fileName, + PGPPublicKey encKey, + boolean armor, + boolean withIntegrityCheck ) + throws IOException, NoSuchProviderException, PGPException + { + Security.addProvider(new BouncyCastleProvider()); + + if ( armor ) { + out = new ArmoredOutputStream(out); + } + + ByteArrayOutputStream bOut = new ByteArrayOutputStream(); + PGPCompressedDataGenerator comData = new PGPCompressedDataGenerator(PGPCompressedData.ZIP); + + PGPUtil.writeFileToLiteralData( + comData.open(bOut), + PGPLiteralData.BINARY, + new File(fileName)); + + comData.close(); + + BcPGPDataEncryptorBuilder dataEncryptor = new BcPGPDataEncryptorBuilder(PGPEncryptedData.TRIPLE_DES); + dataEncryptor.setWithIntegrityPacket(withIntegrityCheck); + dataEncryptor.setSecureRandom(new SecureRandom()); + + PGPEncryptedDataGenerator encryptedDataGenerator = new PGPEncryptedDataGenerator(dataEncryptor); + encryptedDataGenerator.addMethod(new BcPublicKeyKeyEncryptionMethodGenerator(encKey)); + + byte[] bytes = bOut.toByteArray(); + OutputStream cOut = encryptedDataGenerator.open(out, bytes.length); + cOut.write(bytes); + cOut.close(); + + encryptedDataGenerator.close(); + bOut.close(); + + + if( armor ) { + out.close(); + } + } + + @SuppressWarnings("unchecked") + public static void signEncryptFile( + OutputStream out, + String fileName, + PGPPublicKey publicKey, + PGPSecretKey secretKey, + String password, + boolean armor, + boolean withIntegrityCheck ) + throws Exception + { + + // Initialize Bouncy Castle security provider + Provider provider = new BouncyCastleProvider(); + Security.addProvider(provider); + + if ( armor ) { + out = new ArmoredOutputStream(out); + } + + BcPGPDataEncryptorBuilder dataEncryptor = new BcPGPDataEncryptorBuilder(PGPEncryptedData.TRIPLE_DES); + dataEncryptor.setWithIntegrityPacket(withIntegrityCheck); + dataEncryptor.setSecureRandom(new SecureRandom()); + + PGPEncryptedDataGenerator encryptedDataGenerator = new PGPEncryptedDataGenerator(dataEncryptor); + encryptedDataGenerator.addMethod(new BcPublicKeyKeyEncryptionMethodGenerator(publicKey)); + + OutputStream encryptedOut = encryptedDataGenerator.open(out, new byte[PGPUtils.BUFFER_SIZE]); + + // Initialize compressed data generator + PGPCompressedDataGenerator compressedDataGenerator = new PGPCompressedDataGenerator(PGPCompressedData.ZIP); + OutputStream compressedOut = compressedDataGenerator.open(encryptedOut, new byte[PGPUtils.BUFFER_SIZE]); + + // Initialize signature generator + PGPPrivateKey privateKey = findPrivateKey(secretKey, password.toCharArray()); + + PGPContentSignerBuilder signerBuilder = new BcPGPContentSignerBuilder(secretKey.getPublicKey().getAlgorithm(), + HashAlgorithmTags.SHA1); + + PGPSignatureGenerator signatureGenerator = new PGPSignatureGenerator(signerBuilder); + signatureGenerator.init(PGPSignature.BINARY_DOCUMENT, privateKey); + + boolean firstTime = true; + Iterator it = secretKey.getPublicKey().getUserIDs(); + while( it.hasNext() && firstTime ) { + PGPSignatureSubpacketGenerator spGen = new PGPSignatureSubpacketGenerator(); + spGen.addSignerUserID(false, it.next()); + signatureGenerator.setHashedSubpackets(spGen.generate()); + // Exit the loop after the first iteration + firstTime = false; + } + signatureGenerator.generateOnePassVersion(false).encode(compressedOut); + + // Initialize literal data generator + PGPLiteralDataGenerator literalDataGenerator = new PGPLiteralDataGenerator(); + OutputStream literalOut = literalDataGenerator.open( + compressedOut, + PGPLiteralData.BINARY, + fileName, + new Date(), + new byte[PGPUtils.BUFFER_SIZE]); + + // Main loop - read the "in" stream, compress, encrypt and write to the "out" stream + FileInputStream in = new FileInputStream(fileName); + byte[] buf = new byte[PGPUtils.BUFFER_SIZE]; + int len; + while( (len = in.read(buf)) > 0 ) { + literalOut.write(buf, 0, len); + signatureGenerator.update(buf, 0, len); + } + + literalOut.close(); + in.close(); + literalDataGenerator.close(); + // Generate the signature, compress, encrypt and write to the "out" stream + signatureGenerator.generate().encode(compressedOut); + compressedDataGenerator.close(); + encryptedDataGenerator.close(); + if ( armor ) { + out.close(); + } + } + + public static boolean verifyFile( + InputStream in, + InputStream keyIn, + String extractContentFile ) + throws Exception + { + in = PGPUtil.getDecoderStream(in); + + PGPObjectFactory pgpFact = new PGPObjectFactory(in, new BcKeyFingerprintCalculator()); + PGPCompressedData c1 = (PGPCompressedData) pgpFact.nextObject(); + + pgpFact = new PGPObjectFactory(c1.getDataStream(), new BcKeyFingerprintCalculator()); + + PGPOnePassSignatureList p1 = (PGPOnePassSignatureList) pgpFact.nextObject(); + + PGPOnePassSignature ops = p1.get(0); + + PGPLiteralData p2 = (PGPLiteralData) pgpFact.nextObject(); + + InputStream dIn = p2.getInputStream(); + + IOUtils.copy(dIn, new FileOutputStream(extractContentFile)); + + int ch; + PGPPublicKeyRingCollection pgpRing = new PGPPublicKeyRingCollection(PGPUtil.getDecoderStream(keyIn), new BcKeyFingerprintCalculator()); + + PGPPublicKey key = pgpRing.getPublicKey(ops.getKeyID()); + + FileOutputStream out = new FileOutputStream(p2.getFileName()); + + ops.init(new BcPGPContentVerifierBuilderProvider(), key); + + while( (ch = dIn.read()) >= 0 ) + { + ops.update((byte) ch); + out.write(ch); + } + + out.close(); + + PGPSignatureList p3 = (PGPSignatureList) pgpFact.nextObject(); + return ops.verify(p3.get(0)); + } + + /** + * From LockBox Lobs PGP Encryption tools. + * http://www.lockboxlabs.org/content/downloads + * + * I didn't think it was worth having to import a 4meg lib for three methods + * + * @param key + * @return + */ + public static boolean isForEncryption( PGPPublicKey key ) + { + if ( key.getAlgorithm() == PublicKeyAlgorithmTags.RSA_SIGN + || key.getAlgorithm() == PublicKeyAlgorithmTags.DSA + || key.getAlgorithm() == PublicKeyAlgorithmTags.ECDH + || key.getAlgorithm() == PublicKeyAlgorithmTags.ECDSA ) + { + return false; + } + + return hasKeyFlags(key, KeyFlags.ENCRYPT_COMMS | KeyFlags.ENCRYPT_STORAGE); + } + + /** + * From LockBox Lobs PGP Encryption tools. + * http://www.lockboxlabs.org/content/downloads + * + * I didn't think it was worth having to import a 4meg lib for three methods + * + * @param key + * @return + */ + @SuppressWarnings("unchecked") + private static boolean hasKeyFlags( PGPPublicKey encKey, int keyUsage ) { + if ( encKey.isMasterKey() ) { + for( int i = 0; i != PGPUtils.MASTER_KEY_CERTIFICATION_TYPES.length; i++ ) { + for( Iterator eIt = encKey.getSignaturesOfType(PGPUtils.MASTER_KEY_CERTIFICATION_TYPES[i]); eIt.hasNext(); ) { + PGPSignature sig = eIt.next(); + if ( !isMatchingUsage(sig, keyUsage) ) { + return false; + } + } + } + } + else { + for( Iterator eIt = encKey.getSignaturesOfType(PGPSignature.SUBKEY_BINDING); eIt.hasNext(); ) { + PGPSignature sig = eIt.next(); + if ( !isMatchingUsage(sig, keyUsage) ) { + return false; + } + } + } + return true; + } + + /** + * From LockBox Lobs PGP Encryption tools. + * http://www.lockboxlabs.org/content/downloads + * + * I didn't think it was worth having to import a 4meg lib for three methods + * + * @param key + * @return + */ + private static boolean isMatchingUsage( PGPSignature sig, int keyUsage ) { + if ( sig.hasSubpackets() ) { + PGPSignatureSubpacketVector sv = sig.getHashedSubPackets(); + if ( sv.hasSubpacket(PGPUtils.KEY_FLAGS) ) { + if ( (sv.getKeyFlags() == 0 && keyUsage == 0) ) { + return false; + } + } + } + return true; + } + } \ No newline at end of file diff --git a/src/EmailModuleWithTemplates/javasource/system/UserActionsRegistrar.java b/src/EmailModuleWithTemplates/javasource/system/UserActionsRegistrar.java index ac5e80d..1745f06 100644 --- a/src/EmailModuleWithTemplates/javasource/system/UserActionsRegistrar.java +++ b/src/EmailModuleWithTemplates/javasource/system/UserActionsRegistrar.java @@ -7,11 +7,11 @@ public class UserActionsRegistrar public void registerActions(IActionRegistrator registrator) { registrator.bundleComponentLoaded(); - registrator.registerUserAction(emailtemplate.actions.ConvertHTMLBodyToPlainText.class); - registrator.registerUserAction(emailtemplate.actions.CopyAttachmentContent.class); - registrator.registerUserAction(emailtemplate.actions.ReplaceCustomToken.class); - registrator.registerUserAction(emailtemplate.actions.ReplaceEmailTemplateTokens.class); - registrator.registerUserAction(emailtemplate.actions.SendEmail.class); + registrator.registerUserAction(emailmodulewithtemplates.actions.ConvertHTMLBodyToPlainText.class); + registrator.registerUserAction(emailmodulewithtemplates.actions.CopyAttachmentContent.class); + registrator.registerUserAction(emailmodulewithtemplates.actions.ReplaceCustomToken.class); + registrator.registerUserAction(emailmodulewithtemplates.actions.ReplaceEmailTemplateTokens.class); + registrator.registerUserAction(emailmodulewithtemplates.actions.SendEmail.class); registrator.registerUserAction(encryption.actions.DecryptString.class); registrator.registerUserAction(encryption.actions.EncryptString.class); registrator.registerUserAction(encryption.actions.GeneratePGPKeyRing.class);