Skip to content

Commit

Permalink
Update module and test project to version 5.4.1 (#111)
Browse files Browse the repository at this point in the history
* Update module and test project to version 5.4.1

* Update module and test project to version 5.4.1
  • Loading branch information
MxMurshed authored Jan 9, 2024
1 parent 00cc590 commit 714107d
Show file tree
Hide file tree
Showing 120 changed files with 233 additions and 194 deletions.
54 changes: 35 additions & 19 deletions build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -22,16 +22,16 @@ apply plugin: 'org.owasp.dependencycheck'
apply plugin: 'com.github.ben-manes.versions'

project.ext {
PNC_VERSION = '5.1.5'
MXBUILD_VERSION = '8.9.0.5487'
PNC_VERSION = '5.4.1'
MXBUILD_VERSION = '8.18.0.13281'
MODULE_NAME = 'PushNotifications'
}

def runtimeLibs = "$buildDir/runtime/bundles"
def monoPath = '/Library/Frameworks/Mono.framework/Versions/Current/Commands'

def userLibDir = "$projectDir/test/userlib"
def testProject = "$projectDir/test/PushNotfications.mpr"
def userLibDir = "$projectDir/userlib-generated"
def testProject = "$projectDir/PushNotfications.mpr"

configurations {
tar
Expand All @@ -41,7 +41,7 @@ repositories {
mavenCentral()
ivy {
url 'https://cdn.mendix.com/'
layout 'pattern', {
patternLayout {
artifact '/[organisation]/[module]-[revision].[ext]'
}
metadataSources {
Expand All @@ -51,35 +51,51 @@ repositories {
}

dependencies {
compile group: 'commons-codec', name: 'commons-codec', version: '1.13'
compile group: 'com.google.api-client', name: 'google-api-client', version: '1.30.4'
compile group: 'io.netty', name: 'netty-tcnative', version: '2.0.26.Final'
compile group: 'com.turo', name: 'pushy', version: '0.13.9'
compile group: 'org.slf4j', name: 'slf4j-api', version: '1.7.28'
implementation group: 'org.slf4j', name: 'slf4j-api', version: '2.0.7'
implementation group: 'commons-codec', name: 'commons-codec', version: '1.15'

compile('io.netty:netty-codec-http2') {
implementation('com.google.auth:google-auth-library-oauth2-http:1.7.0') {
exclude group: 'com.google.code.gson', module: 'gson'
exclude group: 'com.google.guava', module: 'guava'
}

implementation('com.google.guava:guava') {
version {
strictly '4.1.53.Final'
strictly '32.0.1-android'
}
}

implementation('com.turo:pushy:0.13.10') {
exclude group: 'io.netty', module: 'netty-codec-http2'
exclude group: 'io.netty', module: 'netty-handler-proxy'
exclude group: 'io.netty', module: 'netty-resolver-dns'
exclude group: 'com.google.code.gson', module: 'gson'
}

implementation('io.netty:netty-codec-http2') {
version {
strictly '4.1.101.Final'
}
}

compile('io.netty:netty-handler-proxy') {
implementation('io.netty:netty-handler-proxy') {
version {
strictly '4.1.53.Final'
strictly '4.1.101.Final'
}
}

compile('io.netty:netty-resolver-dns') {
implementation('io.netty:netty-resolver-dns') {
version {
strictly '4.1.53.Final'
strictly '4.1.101.Final'
}
}

compile('io.netty:netty-all') {
implementation('com.google.code.gson:gson') {
version {
strictly '4.1.53.Final'
strictly '2.9.0'
}
}

tar "runtime:mxbuild:${project.MXBUILD_VERSION}@tar.gz"
}

Expand All @@ -99,7 +115,7 @@ extractModule.doFirst {

task copyToUserlib( type: Copy ) {
into userLibDir
from configurations.runtime
from configurations.runtimeClasspath
eachFile { fileCopyDetails ->
def requiredLibFlag = new File(destinationDir, "${fileCopyDetails.name}.${project.MODULE_NAME}.RequiredLib")
requiredLibFlag.write ''
Expand Down
2 changes: 1 addition & 1 deletion package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "MxPushNotifications",
"version": "4.0.6",
"version": "5.4.1",
"description": "Phonegap widget for notifications",
"license": "Apache 2.0",
"author": "Mendix Technology B.V.",
Expand Down
Binary file modified test/PushNotfications.mpr
Binary file not shown.
117 changes: 79 additions & 38 deletions test/javasource/encryption/actions/DecryptString.java
Original file line number Diff line number Diff line change
Expand Up @@ -11,11 +11,14 @@

import java.security.InvalidAlgorithmParameterException;
import java.util.Base64;
import javax.crypto.AEADBadTagException;
import javax.crypto.BadPaddingException;
import javax.crypto.Cipher;
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;
Expand All @@ -25,38 +28,36 @@ public class DecryptString extends CustomJavaAction<java.lang.String>
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
}

Expand All @@ -70,18 +71,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 encrypted with a different key or not encrypted at all";

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"); // ignore Snyk Code warning; false positive

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"); // ignore Snyk Code warning; false positive

byte[] iv = Base64.getDecoder().decode(s[0].getBytes());
byte[] encryptedData = Base64.getDecoder().decode(s[1].getBytes());
Expand All @@ -98,19 +135,24 @@ private String decryptUsingGcm() throws Exception {
}

private boolean isEncryptedWithWrongKey(String message) {
return
message.equals( "Wrong IV length: must be 16 bytes long") ||
message.equals("Given final block not properly padded");
return message.contains("Wrong IV length") ||
message.contains("Given final block not properly padded") ||
message.contains("Tag mismatch");
}

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");
Cipher c = Cipher.getInstance("AES/CBC/PKCS5PADDING"); // ignore Snyk Code warning; we decrypt only (for backward compatibility)
SecretKeySpec k = new SecretKeySpec(this.legacyKey.getBytes(), "AES"); // ignore Snyk Code warning; false positive

byte[] iv = Base64.getDecoder().decode(s[0].getBytes());
byte[] encryptedData = Base64.getDecoder().decode(s[1].getBytes());
Expand All @@ -125,12 +167,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
}
26 changes: 19 additions & 7 deletions test/javasource/encryption/actions/EncryptString.java
Original file line number Diff line number Diff line change
Expand Up @@ -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");
SecretKeySpec k = new SecretKeySpec(this.key.getBytes(), "AES");
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"); // ignore Snyk Code warning; false positive
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
}

Expand All @@ -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
}
4 changes: 2 additions & 2 deletions test/javasource/encryption/actions/PGPDecryptDocument.java
Original file line number Diff line number Diff line change
Expand Up @@ -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
*
Expand Down
Loading

0 comments on commit 714107d

Please sign in to comment.