diff --git a/.github/workflows/codacy.yml b/.github/workflows/codacy.yml index 99eef58..d98288d 100644 --- a/.github/workflows/codacy.yml +++ b/.github/workflows/codacy.yml @@ -18,9 +18,9 @@ on: branches: [ main, develop, main1, release* ] pull_request: # The branches below must be a subset of the branches above - branches: [ main ] + branches: [ main, feature/*, bugfix/*, hotfix/* ] schedule: - - cron: '42 10 * * 3' + - cron: '0 11,23 * * *' permissions: contents: read diff --git a/CHANGES b/CHANGES index 0ff63e0..e441804 100644 --- a/CHANGES +++ b/CHANGES @@ -1,5 +1,7 @@ -- release 2.6.1 - enhanced security: replace secpr5121 with secp256r1 for default ECGenParameterSpec +- enhanced security: Use of a cryptographic algorithm with insufficient key size - Throw InvalidAlgorithmParameterException when use of a cryptographic algorithm with insufficient key size to call EncryptorUtil.generateKeyPair() +- enhanced security: fixed polynomial regular expression used on uncontrolled data (FormatterUtil) - enhanced logging: log detailed information and stack trace health check failure - enhanced logging: Netty channel handler exception will be logged only when: 1. run with -debug diff --git a/src/main/java/org/summerboot/jexpress/security/EncryptorUtil.java b/src/main/java/org/summerboot/jexpress/security/EncryptorUtil.java index 346d6e3..8308f84 100644 --- a/src/main/java/org/summerboot/jexpress/security/EncryptorUtil.java +++ b/src/main/java/org/summerboot/jexpress/security/EncryptorUtil.java @@ -489,14 +489,27 @@ public static KeyPair generateKeyPair(String keyfactoryAlgorithm, int size) thro KeyPairGenerator kpg; switch (keyfactoryAlgorithm.toUpperCase()) { case "RSA" -> { + if (size < 2048) { + throw new InvalidAlgorithmParameterException("RSA key size must be at least 2048 bits."); + } kpg = KeyPairGenerator.getInstance("RSA"); kpg.initialize(size); } case "EC" -> { + if (size < 256) { + throw new InvalidAlgorithmParameterException("EC key size must be at least 256 bits."); + } kpg = KeyPairGenerator.getInstance("EC"); ECGenParameterSpec spec = getECCurveName(size); kpg.initialize(spec); } + case "DSA", "DH" -> { + if (size < 2048) { + throw new InvalidAlgorithmParameterException(keyfactoryAlgorithm + " key size must be at least 2048 bits."); + } + kpg = KeyPairGenerator.getInstance(keyfactoryAlgorithm.toUpperCase()); + kpg.initialize(size); + } default -> throw new NoSuchAlgorithmException(keyfactoryAlgorithm); } diff --git a/src/main/java/org/summerboot/jexpress/util/FormatterUtil.java b/src/main/java/org/summerboot/jexpress/util/FormatterUtil.java index 784c478..c3d1342 100644 --- a/src/main/java/org/summerboot/jexpress/util/FormatterUtil.java +++ b/src/main/java/org/summerboot/jexpress/util/FormatterUtil.java @@ -73,29 +73,72 @@ public static String[] parseLines(String txt) { return StringUtils.isBlank(txt) ? EMPTY_STR_ARRAY : txt.split("\\r?\\n"); } - public static String[] parseDsv(String csv, String delimiter) { - if (StringUtils.isBlank(delimiter) || ",".equals(delimiter)) { - return parseCsv(csv); + public static String[] parseDsv(String dsv, String delimiter) { + return parseDsv(dsv, delimiter, true); + } + + public static String[] parseDsv(String dsv, String delimiter, boolean trim) { + if (StringUtils.isBlank(dsv)) { + return EMPTY_STR_ARRAY; } - return StringUtils.isBlank(csv) ? EMPTY_STR_ARRAY : csv.trim().split("\\s*" + delimiter + "\\s*"); + + if (trim) { + // Replace all consecutive spaces or delimiter characters with a single delimiter character + dsv = dsv.trim().replaceAll("\\s*" + delimiter + "\\s*", delimiter); + } + // Use StringUtils.split, which does not use regular expressions + return StringUtils.split(dsv, delimiter); + } + + public static String[] parsePsv(String psv) { + return parsePsv(psv, true); } - public static String[] parsePsv(String csv) { - return StringUtils.isBlank(csv) ? EMPTY_STR_ARRAY : csv.trim().split(REGEX_PSV); + public static String[] parsePsv(String psv, boolean trim) { + //return StringUtils.isBlank(csv) ? EMPTY_STR_ARRAY : csv.trim().split(REGEX_PSV); + if (StringUtils.isBlank(psv)) { + return EMPTY_STR_ARRAY; + } + if (trim) { + // Replace all consecutive spaces or delimiter characters with a single delimiter character + psv = psv.trim().replaceAll("[\\s|]+", "|"); + } + // Use StringUtils.split, which does not use regular expressions + return StringUtils.split(psv, '|'); } public static String[] parseCsv(String csv) { - return StringUtils.isBlank(csv) ? EMPTY_STR_ARRAY : csv.trim().split(REGEX_CSV); + return parseCsv(csv, true); + } + + public static String[] parseCsv(String csv, boolean trim) { + //return StringUtils.isBlank(csv) ? EMPTY_STR_ARRAY : csv.trim().split(REGEX_CSV); + if (StringUtils.isBlank(csv)) { + return EMPTY_STR_ARRAY; + } + //return StringUtils.isBlank(csv) ? EMPTY_STR_ARRAY : StringUtils.split(csv); + if (trim) { + // Replace all consecutive spaces or delimiter characters with a single delimiter character + csv = csv.trim().replaceAll("\\s*,\\s*", ","); + } + // Use StringUtils.split, which does not use regular expressions + return StringUtils.split(csv, ','); } public static String[] parseURL(String url) { - return StringUtils.isBlank(url) ? EMPTY_STR_ARRAY : url.trim().split(REGEX_URL); + return parseURL(url, true); } public static String[] parseURL(String url, boolean trim) { - return StringUtils.isBlank(url) - ? EMPTY_STR_ARRAY - : trim ? url.trim().split(REGEX_URL) : url.split(REGEX_URL); + //return StringUtils.isBlank(url) ? EMPTY_STR_ARRAY : url.trim().split(REGEX_URL); + if (StringUtils.isBlank(url)) { + return EMPTY_STR_ARRAY; + } + if (trim) { + // Replace all consecutive spaces or delimiter characters with a single delimiter character + url = url.trim().replaceAll("\\s*/\\s*", "/"); + } + return StringUtils.split(url, '/'); } public static String parseUrlQueryParam(String url, Map queryParam) { @@ -239,22 +282,28 @@ public static String b2n(String s) { * BindAddresses = 192.168.1.10:8445, 127.0.0.1:8446, 0.0.0.0:8447 */ public static Map parseBindingAddresss(String bindAddresses) { - //int[] ports = Arrays.stream(portsStr).mapToInt(Integer::parseInt).toArray(); - Map ret = new HashMap<>(); - String[] addrs = parseCsv(bindAddresses); - for (String addr : addrs) { - String[] ap = addr.trim().split(REGEX_BINDING_MAP); - ret.put(ap[0], Integer.parseInt(ap[1])); - } - return ret; + Map stringMap = parseMap(bindAddresses, true); + Map integerMap = stringMap.entrySet().stream() + .collect(Collectors.toMap( + Map.Entry::getKey, // key remains the same + entry -> Integer.valueOf(entry.getValue()) // convert value to Integer + )); + return integerMap; } public static Map parseMap(String mapCVS) { - //int[] ports = Arrays.stream(portsStr).mapToInt(Integer::parseInt).toArray(); + return parseMap(mapCVS, true); + } + + public static Map parseMap(String mapCVS, boolean trim) { Map ret = new HashMap<>(); - String[] mapKeyValues = parseCsv(mapCVS); + String[] mapKeyValues = parseCsv(mapCVS, true); for (String mapKeyValue : mapKeyValues) { - String[] ap = mapKeyValue.trim().split(REGEX_BINDING_MAP); + //String[] ap = mapKeyValue.trim().split(REGEX_BINDING_MAP); + if (trim) { + mapKeyValue = mapKeyValue.trim().replaceAll("\\s*:\\s*", ":"); + } + String[] ap = StringUtils.split(mapKeyValue, ':'); ret.put(ap[0], ap[1]); } return ret;