From 0ac4c8f98d3b02eeafc1fb34ca12571066c88e3e Mon Sep 17 00:00:00 2001 From: TharmiganK Date: Fri, 17 Oct 2025 18:38:57 +0530 Subject: [PATCH 01/10] Refactor static code rule implementation --- .../StaticCodeAnalyzerTest.java | 23 +- .../ballerina_packages/rule4/Ballerina.toml | 8 - .../ballerina_packages/rule4/main.bal | 29 - .../expected_output/rule4.json | 22 - .../CryptoAnalyzerUtils.java | 581 +++++++----------- .../CryptoCipherAlgorithmAnalyzer.java | 288 +-------- .../CryptoFunctionRulesEngine.java | 62 ++ .../staticcodeanalyzer/CryptoRule.java | 4 +- .../staticcodeanalyzer/FunctionContext.java | 230 +++++++ .../AvoidFastHashAlgorithmsRule.java | 164 +++++ .../AvoidReusingCounterModeVectorsRule.java | 94 +++ .../AvoidWeakCipherAlgorithmsRule.java | 69 +++ .../functionrules/CryptoFunctionRule.java | 50 ++ compiler-plugin/src/main/resources/rules.json | 5 - 14 files changed, 912 insertions(+), 717 deletions(-) delete mode 100644 compiler-plugin-tests/src/test/resources/static_code_analyzer/ballerina_packages/rule4/Ballerina.toml delete mode 100644 compiler-plugin-tests/src/test/resources/static_code_analyzer/ballerina_packages/rule4/main.bal delete mode 100644 compiler-plugin-tests/src/test/resources/static_code_analyzer/expected_output/rule4.json create mode 100644 compiler-plugin/src/main/java/io/ballerina/stdlib/crypto/compiler/staticcodeanalyzer/CryptoFunctionRulesEngine.java create mode 100644 compiler-plugin/src/main/java/io/ballerina/stdlib/crypto/compiler/staticcodeanalyzer/FunctionContext.java create mode 100644 compiler-plugin/src/main/java/io/ballerina/stdlib/crypto/compiler/staticcodeanalyzer/functionrules/AvoidFastHashAlgorithmsRule.java create mode 100644 compiler-plugin/src/main/java/io/ballerina/stdlib/crypto/compiler/staticcodeanalyzer/functionrules/AvoidReusingCounterModeVectorsRule.java create mode 100644 compiler-plugin/src/main/java/io/ballerina/stdlib/crypto/compiler/staticcodeanalyzer/functionrules/AvoidWeakCipherAlgorithmsRule.java create mode 100644 compiler-plugin/src/main/java/io/ballerina/stdlib/crypto/compiler/staticcodeanalyzer/functionrules/CryptoFunctionRule.java diff --git a/compiler-plugin-tests/src/test/java/io/ballerina/stdlib/crypto/compiler/staticcodeanalyzer/StaticCodeAnalyzerTest.java b/compiler-plugin-tests/src/test/java/io/ballerina/stdlib/crypto/compiler/staticcodeanalyzer/StaticCodeAnalyzerTest.java index 81c3ff62..3391cc15 100644 --- a/compiler-plugin-tests/src/test/java/io/ballerina/stdlib/crypto/compiler/staticcodeanalyzer/StaticCodeAnalyzerTest.java +++ b/compiler-plugin-tests/src/test/java/io/ballerina/stdlib/crypto/compiler/staticcodeanalyzer/StaticCodeAnalyzerTest.java @@ -49,7 +49,6 @@ import static io.ballerina.scan.RuleKind.VULNERABILITY; import static io.ballerina.stdlib.crypto.compiler.staticcodeanalyzer.CryptoRule.AVOID_FAST_HASH_ALGORITHMS; import static io.ballerina.stdlib.crypto.compiler.staticcodeanalyzer.CryptoRule.AVOID_REUSING_COUNTER_MODE_VECTORS; -import static io.ballerina.stdlib.crypto.compiler.staticcodeanalyzer.CryptoRule.AVOID_USING_UNSECURE_RANDOM_NUMBER_GENERATORS; import static io.ballerina.stdlib.crypto.compiler.staticcodeanalyzer.CryptoRule.AVOID_WEAK_CIPHER_ALGORITHMS; import static java.nio.charset.StandardCharsets.UTF_8; @@ -118,28 +117,19 @@ private void validateRules(List rules) { "ballerina/crypto:3", AVOID_REUSING_COUNTER_MODE_VECTORS.getDescription(), VULNERABILITY); - Assertions.assertRule( - rules, - "ballerina/crypto:4", - AVOID_USING_UNSECURE_RANDOM_NUMBER_GENERATORS.getDescription(), - VULNERABILITY); } private void validateIssues(CryptoRule rule, List issues) { switch (rule) { case AVOID_WEAK_CIPHER_ALGORITHMS: - Assert.assertEquals(issues.size(), 6); + Assert.assertEquals(issues.size(), 4); Assertions.assertIssue(issues, 0, "ballerina/crypto:1", "aes_cbc.bal", 30, 30, Source.BUILT_IN); - Assertions.assertIssue(issues, 1, "ballerina/crypto:4", "aes_cbc.bal", + Assertions.assertIssue(issues, 1, "ballerina/crypto:1", "aes_cbc_as_import.bal", 30, 30, Source.BUILT_IN); - Assertions.assertIssue(issues, 2, "ballerina/crypto:1", "aes_cbc_as_import.bal", - 30, 30, Source.BUILT_IN); - Assertions.assertIssue(issues, 3, "ballerina/crypto:4", "aes_cbc_as_import.bal", - 30, 30, Source.BUILT_IN); - Assertions.assertIssue(issues, 4, "ballerina/crypto:1", "aes_ecb.bal", + Assertions.assertIssue(issues, 2, "ballerina/crypto:1", "aes_ecb.bal", 26, 26, Source.BUILT_IN); - Assertions.assertIssue(issues, 5, "ballerina/crypto:1", "aes_ecb_as_import.bal", + Assertions.assertIssue(issues, 3, "ballerina/crypto:1", "aes_ecb_as_import.bal", 26, 26, Source.BUILT_IN); break; case AVOID_FAST_HASH_ALGORITHMS: @@ -196,11 +186,6 @@ private void validateIssues(CryptoRule rule, List issues) { Assertions.assertIssue(issues, 5, "ballerina/crypto:3", "mod_var_pos_arg.bal", 23, 23, Source.BUILT_IN); break; - case AVOID_USING_UNSECURE_RANDOM_NUMBER_GENERATORS: - Assert.assertEquals(issues.size(), 1); - Assertions.assertIssue(issues, 0, "ballerina/crypto:4", "main.bal", - 27, 27, Source.BUILT_IN); - break; default: Assert.fail("Unhandled rule in validateIssues: " + rule); break; diff --git a/compiler-plugin-tests/src/test/resources/static_code_analyzer/ballerina_packages/rule4/Ballerina.toml b/compiler-plugin-tests/src/test/resources/static_code_analyzer/ballerina_packages/rule4/Ballerina.toml deleted file mode 100644 index 49663051..00000000 --- a/compiler-plugin-tests/src/test/resources/static_code_analyzer/ballerina_packages/rule4/Ballerina.toml +++ /dev/null @@ -1,8 +0,0 @@ -[package] -org = "wso2" -name = "rule4" -version = "0.1.0" -distribution = "2201.12.3" - -[build-options] -observabilityIncluded = true diff --git a/compiler-plugin-tests/src/test/resources/static_code_analyzer/ballerina_packages/rule4/main.bal b/compiler-plugin-tests/src/test/resources/static_code_analyzer/ballerina_packages/rule4/main.bal deleted file mode 100644 index 8d0d5d01..00000000 --- a/compiler-plugin-tests/src/test/resources/static_code_analyzer/ballerina_packages/rule4/main.bal +++ /dev/null @@ -1,29 +0,0 @@ -// Copyright (c) 2025 WSO2 LLC. (http://www.wso2.org) -// -// WSO2 LLC. licenses this file to you under the Apache License, -// Version 2.0 (the "License"); you may not use this file except -// in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, -// software distributed under the License is distributed on an -// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY -// KIND, either express or implied. See the License for the -// specific language governing permissions and limitations -// under the License. - -import ballerina/crypto; -import ballerina/random; - -public isolated function main() returns error? { - string data = "Hello, World!"; - byte[16] initialVector = []; - byte[16] key = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15]; - byte[] dataBytes = data.toBytes(); - foreach int i in 0 ... 15 { - initialVector[i] = (check random:createIntInRange(0, 255)); - } - byte[] _ = check crypto:encryptAesGcm(dataBytes, key, initialVector); -} diff --git a/compiler-plugin-tests/src/test/resources/static_code_analyzer/expected_output/rule4.json b/compiler-plugin-tests/src/test/resources/static_code_analyzer/expected_output/rule4.json deleted file mode 100644 index 51c655e8..00000000 --- a/compiler-plugin-tests/src/test/resources/static_code_analyzer/expected_output/rule4.json +++ /dev/null @@ -1,22 +0,0 @@ -[ - { - "location": { - "filePath": "main.bal", - "startLine": 27, - "endLine": 27, - "startColumn": 21, - "endColumn": 72, - "startOffset": 1058, - "length": 51 - }, - "rule": { - "id": "ballerina/crypto:4", - "numericId": 4, - "description": "Secure random number generators should not output predictable values", - "ruleKind": "VULNERABILITY" - }, - "source": "BUILT_IN", - "fileName": "rule4/main.bal", - "filePath": "module-ballerina-crypto/compiler-plugin-tests/src/test/resources/static_code_analyzer/ballerina_packages/rule4/main.bal" - } -] diff --git a/compiler-plugin/src/main/java/io/ballerina/stdlib/crypto/compiler/staticcodeanalyzer/CryptoAnalyzerUtils.java b/compiler-plugin/src/main/java/io/ballerina/stdlib/crypto/compiler/staticcodeanalyzer/CryptoAnalyzerUtils.java index e79049b7..05486b15 100644 --- a/compiler-plugin/src/main/java/io/ballerina/stdlib/crypto/compiler/staticcodeanalyzer/CryptoAnalyzerUtils.java +++ b/compiler-plugin/src/main/java/io/ballerina/stdlib/crypto/compiler/staticcodeanalyzer/CryptoAnalyzerUtils.java @@ -18,30 +18,43 @@ package io.ballerina.stdlib.crypto.compiler.staticcodeanalyzer; +import io.ballerina.compiler.api.ModuleID; import io.ballerina.compiler.api.SemanticModel; +import io.ballerina.compiler.api.symbols.ConstantSymbol; +import io.ballerina.compiler.api.symbols.FunctionSymbol; +import io.ballerina.compiler.api.symbols.ParameterSymbol; import io.ballerina.compiler.api.symbols.Symbol; +import io.ballerina.compiler.api.values.ConstantValue; +import io.ballerina.compiler.syntax.tree.AssignmentStatementNode; import io.ballerina.compiler.syntax.tree.BasicLiteralNode; +import io.ballerina.compiler.syntax.tree.BindingPatternNode; +import io.ballerina.compiler.syntax.tree.BlockStatementNode; import io.ballerina.compiler.syntax.tree.CaptureBindingPatternNode; import io.ballerina.compiler.syntax.tree.ExpressionNode; +import io.ballerina.compiler.syntax.tree.FunctionArgumentNode; import io.ballerina.compiler.syntax.tree.FunctionBodyBlockNode; -import io.ballerina.compiler.syntax.tree.ListConstructorExpressionNode; +import io.ballerina.compiler.syntax.tree.FunctionCallExpressionNode; +import io.ballerina.compiler.syntax.tree.IdentifierToken; import io.ballerina.compiler.syntax.tree.ModuleMemberDeclarationNode; import io.ballerina.compiler.syntax.tree.ModulePartNode; import io.ballerina.compiler.syntax.tree.ModuleVariableDeclarationNode; +import io.ballerina.compiler.syntax.tree.NameReferenceNode; +import io.ballerina.compiler.syntax.tree.NamedArgumentNode; import io.ballerina.compiler.syntax.tree.Node; import io.ballerina.compiler.syntax.tree.NodeList; -import io.ballerina.compiler.syntax.tree.SimpleNameReferenceNode; +import io.ballerina.compiler.syntax.tree.PositionalArgumentNode; +import io.ballerina.compiler.syntax.tree.SeparatedNodeList; import io.ballerina.compiler.syntax.tree.StatementNode; +import io.ballerina.compiler.syntax.tree.SyntaxKind; import io.ballerina.compiler.syntax.tree.VariableDeclarationNode; import io.ballerina.projects.Document; import io.ballerina.projects.DocumentId; import io.ballerina.projects.Module; -import io.ballerina.projects.plugins.SyntaxNodeAnalysisContext; +import java.util.HashMap; +import java.util.List; +import java.util.Map; import java.util.Optional; -import java.util.Set; -import java.util.function.BiPredicate; -import java.util.function.Predicate; /** * Utility class containing helper methods for crypto cipher algorithm analysis. @@ -49,438 +62,286 @@ * variable analysis. */ public final class CryptoAnalyzerUtils { - public static final String ENCRYPT_AES_ECB = "encryptAesEcb"; - public static final String ENCRYPT_AES_CBC = "encryptAesCbc"; - public static final String ENCRYPT_AES_GCM = "encryptAesGcm"; - public static final String HASH_BCRYPT = "hashBcrypt"; - public static final String HASH_ARGON2 = "hashArgon2"; - public static final String HASH_PBKDF2 = "hashPbkdf2"; - public static final String ITERATIONS = "iterations"; - public static final String MEMORY = "memory"; - public static final String PARALLELISM = "parallelism"; - public static final String CREATE_INT_IN_RANGE = "createIntInRange"; - public static final int BCRYPT_RECOMMENDED_WORK_FACTOR = 10; - public static final int ARGON2_RECOMMENDED_ITERATIONS = 2; - public static final int ARGON2_RECOMMENDED_MEMORY = 19456; - public static final int ARGON2_RECOMMENDED_PARALLELISM = 1; - public static final int PBKDF2_RECOMMENDED_ITERATIONS = 100000; + private static final String BALLERINA_ORG = "ballerina"; + private static final String CRYPTO = "crypto"; /** - * Enum representing the different parameters of Argon2. - */ - public enum ArgonParameter { - ITERATIONS, - MEMORY, - PARALLELISM - } - - private CryptoAnalyzerUtils() { - // Prevent instantiation - } - - /** - * Checks if the given function name corresponds to a weak cipher function. - * - * @param functionName the name of the function - * @return true if the function is a weak cipher function, false otherwise - */ - public static boolean isWeakCipherFunction(String functionName) { - return ENCRYPT_AES_ECB.equals(functionName) || ENCRYPT_AES_CBC.equals(functionName); - } - - /** - * Checks if the given function requires secure initialization vectors. + * Retrieves the FunctionSymbol for a given FunctionCallExpressionNode if it belongs to the Ballerina + * crypto module. * - * @param functionName the name of the function - * @return true if the function requires secure IVs, false otherwise + * @param functionCall the function call expression node + * @param semanticModel the semantic model + * @return an Optional containing the FunctionSymbol if it belongs to the crypto module, otherwise empty */ - public static boolean requiresSecureIV(String functionName) { - return ENCRYPT_AES_GCM.equals(functionName) || ENCRYPT_AES_CBC.equals(functionName); - } - - /** - * Checks if the given expression represents a weak bcrypt work factor. - * - * @param expression the expression to check - * @return true if the work factor is weak, false otherwise - */ - public static boolean isWeakBcryptParameter(ExpressionNode expression) { - if (expression instanceof BasicLiteralNode basicLiteral) { - try { - return Integer.parseInt(basicLiteral.literalToken().text()) < BCRYPT_RECOMMENDED_WORK_FACTOR; - } catch (NumberFormatException e) { - return false; - } - } else if (expression instanceof SimpleNameReferenceNode varRef) { - return hasWeakParameterSettings(varRef, CryptoAnalyzerUtils::isWeakBcryptVariable); + public static Optional getCryptoFunctionSymbol(FunctionCallExpressionNode functionCall, + SemanticModel semanticModel) { + Optional functionCallSymbolOptional = semanticModel.symbol(functionCall); + if (functionCallSymbolOptional.isEmpty() + || !(functionCallSymbolOptional.get() instanceof FunctionSymbol functionSymbol) + || functionSymbol.getModule().isEmpty()) { + return Optional.empty(); } - return false; + ModuleID moduleId = (functionCallSymbolOptional.get()).getModule().get().id(); + if (BALLERINA_ORG.equals(moduleId.orgName()) && CRYPTO.equals(moduleId.packageName())) { + return Optional.of(functionSymbol); + } + return Optional.empty(); } /** - * Checks if the given expression represents a weak Argon2 parameter value. + * Retrieves the Document corresponding to the given module and document ID. * - * @param expression the expression to check - * @param paramType the parameter type (iterations, memory, or parallelism) - * @return true if the parameter is weak, false otherwise + * @param module the module + * @param documentId the document ID + * @return the Document for the given module and document ID */ - public static boolean isWeakArgon2Parameter(ExpressionNode expression, ArgonParameter paramType) { - if (expression instanceof BasicLiteralNode basicLiteral) { - try { - int value = Integer.parseInt(basicLiteral.literalToken().text()); - return switch (paramType) { - case ITERATIONS -> value < ARGON2_RECOMMENDED_ITERATIONS; - case MEMORY -> value < ARGON2_RECOMMENDED_MEMORY; - case PARALLELISM -> value < ARGON2_RECOMMENDED_PARALLELISM; - }; - } catch (NumberFormatException e) { - return false; - } - } else if (expression instanceof SimpleNameReferenceNode varRef) { - return hasWeakParameterSettings(varRef, (stmt, varName) -> isWeakArgon2Variable(stmt, varName, paramType)); - } - return false; + public static Document getDocument(Module module, DocumentId documentId) { + return module.document(documentId); } /** - * Checks if the given expression represents a weak PBKDF2 iterations count. + * Unescape the given identifier name by removing leading escape quote and backslashes. * - * @param expression the expression to check - * @return true if the iterations count is weak, false otherwise + * @param identifierName The identifier name to unescape + * @return The unescaped identifier name */ - public static boolean isWeakPbkdf2Parameter(ExpressionNode expression) { - if (expression instanceof BasicLiteralNode basicLiteral) { - try { - int value = Integer.parseInt(basicLiteral.literalToken().text()); - return value < PBKDF2_RECOMMENDED_ITERATIONS; - } catch (NumberFormatException e) { - return false; - } - } else if (expression instanceof SimpleNameReferenceNode varRef) { - return hasWeakParameterSettings(varRef, CryptoAnalyzerUtils::isWeakPbkdf2Variable); + public static String unescapeIdentifier(String identifierName) { + String result = identifierName; + if (result.startsWith("'")) { + result = result.substring(1); } - return false; + return result.replace("\\\\", ""); } - /** - * Checks if the given statement declares a variable with a weak bcrypt work - * factor. - * - * @param stmt the statement to check - * @param varName the name of the variable - * @return true if the statement declares a variable with a weak bcrypt work - * factor, false otherwise - */ - public static boolean isWeakBcryptVariable(Node stmt, String varName) { - return isWeakVariableWithInitializer(stmt, varName, initText -> { - try { - return Integer.parseInt(initText) < BCRYPT_RECOMMENDED_WORK_FACTOR; - } catch (NumberFormatException e) { - return false; - } - }); - } /** - * Checks if the given statement declares a variable with a weak Argon2 - * parameter value. + * Maps parameter names to their corresponding argument expressions in a function call. * - * @param stmt the statement to check - * @param varName the name of the variable - * @param paramType the parameter type (iterations, memory, or parallelism) - * @return true if the statement declares a variable with a weak parameter - * value, false otherwise + * @param params List of ParameterSymbol representing the function parameters + * @param arguments SeparatedNodeList of FunctionArgumentNode representing the function arguments + * @return A map where keys are parameter names and values are the corresponding argument expressions */ - public static boolean isWeakArgon2Variable(Node stmt, String varName, ArgonParameter paramType) { - return isWeakVariableWithInitializer(stmt, varName, initText -> { - try { - int value = Integer.parseInt(initText); - return switch (paramType) { - case ITERATIONS -> value < ARGON2_RECOMMENDED_ITERATIONS; - case MEMORY -> value < ARGON2_RECOMMENDED_MEMORY; - case PARALLELISM -> value < ARGON2_RECOMMENDED_PARALLELISM; - }; - } catch (NumberFormatException e) { - return false; + public static Map getParamExpressions(List params, + SeparatedNodeList arguments) { + Map paramExpressions = new HashMap<>(); + // Argument types: Positional, Named and Rest + // Parameter types: Required, Defaultable, Included and Rest + List paramNames = params.stream() + .map(ParameterSymbol::getName) + .filter(Optional::isPresent) + .map(Optional::get) + .map(CryptoAnalyzerUtils::unescapeIdentifier) + .toList(); + // For each argument we need to find the corresponding parameter name and added it to the map + for (int i = 0; i < arguments.size(); i++) { + FunctionArgumentNode argument = arguments.get(i); + if (argument instanceof PositionalArgumentNode positionalArg) { + if (i < paramNames.size()) { + String paramName = paramNames.get(i); + paramName = unescapeIdentifier(paramName); + ExpressionNode expression = positionalArg.expression(); + paramExpressions.put(paramName, expression); + } + } else if (argument instanceof NamedArgumentNode namedArg) { + String paramName = namedArg.argumentName().name().text(); + paramName = unescapeIdentifier(paramName); + ExpressionNode expression = namedArg.expression(); + paramExpressions.put(paramName, expression); } - }); - } - - /** - * Checks if the given statement declares a variable with a weak PBKDF2 - * iterations count. - * - * @param stmt the statement to check - * @param varName the name of the variable - * @return true if the statement declares a variable with a weak iterations - * count, false otherwise - */ - public static boolean isWeakPbkdf2Variable(Node stmt, String varName) { - return isWeakVariableWithInitializer(stmt, varName, CryptoAnalyzerUtils::checkPbkdf2InitializerValue); - } - - /** - * Checks if the given initializer value is a weak PBKDF2 iterations count. - * - * @param initText the initializer text - * @return true if the initializer value is weak, false otherwise - */ - public static boolean checkPbkdf2InitializerValue(String initText) { - try { - int value = Integer.parseInt(initText); - return value < PBKDF2_RECOMMENDED_ITERATIONS; - } catch (NumberFormatException e) { - return false; + // Not handling RestArgumentNode at the moment as crypto functions do not have rest parameters } + return paramExpressions; } /** - * Checks if the given expression represents a hardcoded initialization vector. - * A hardcoded IV is considered one that contains literal values or array - * literals. + * Retrieves the StatementNode that contains the given FunctionCallExpressionNode. * - * @param expression the expression to check - * @return true if the expression is a hardcoded IV, false otherwise + * @param functionCall the function call expression node + * @return an Optional containing the StatementNode if found, otherwise empty */ - public static boolean isHardcodedIV(ExpressionNode expression, SyntaxNodeAnalysisContext context) { - if (expression instanceof BasicLiteralNode || expression instanceof ListConstructorExpressionNode) { - return true; - } else if (expression instanceof SimpleNameReferenceNode varRef) { - SemanticModel semanticModel = context.semanticModel(); - Optional symbolOpt = semanticModel.symbol(varRef); - return symbolOpt.isPresent() && semanticModel.references(symbolOpt.get()).size() == 2; + public static Optional getStatementNode(FunctionCallExpressionNode functionCall) { + Node parent = functionCall.parent(); + if (parent.kind().equals(SyntaxKind.CHECK_EXPRESSION)) { + parent = parent.parent(); + } + if (parent instanceof StatementNode statementNode) { + return Optional.of(statementNode); } - return false; + return Optional.empty(); } /** - * Generic method to check if a variable declaration has a weak initializer - * value. + * Recursively retrieves the nearest parent block node (FunctionBodyBlockNode or BlockStatementNode) + * of the given node. * - * @param stmt the statement to check - * @param varName the name of the variable - * @param initializerChecker predicate to check if the initializer value is weak - * @return true if the statement declares a variable with a weak value, false - * otherwise + * @param node the starting node + * @return an Optional containing the parent block node if found, otherwise empty */ - public static boolean isWeakVariableWithInitializer(Node stmt, String varName, - Predicate initializerChecker) { - if (stmt instanceof VariableDeclarationNode varDecl && - varDecl.typedBindingPattern().bindingPattern() instanceof CaptureBindingPatternNode capture && - capture.variableName().text().equals(varName) && - varDecl.initializer().isPresent()) { - return initializerChecker.test(varDecl.initializer().get().toSourceCode()); - } - - if (stmt instanceof ModuleVariableDeclarationNode varDecl && - varDecl.typedBindingPattern().bindingPattern() instanceof CaptureBindingPatternNode capture && - capture.variableName().text().equals(varName) && - varDecl.initializer().isPresent()) { - return initializerChecker.test(varDecl.initializer().get().toSourceCode()); - } - - return false; + public static Optional getParentBlockNode(Node node) { + Node parent = node.parent(); + return switch (parent) { + case null -> Optional.empty(); + case FunctionBodyBlockNode functionBody -> Optional.of(functionBody); + case BlockStatementNode blockStatementNode -> Optional.of(blockStatementNode); + default -> getParentBlockNode(parent); + }; } /** - * Checks if the given variable reference refers to a variable with a weak - * parameter value. + * Recursively retrieves the ModulePartNode that contains the given node. * - * @param varRef the variable reference - * @param checker predicate to check if a statement declares a variable with a - * weak value - * @return true if the variable has a weak parameter value, false otherwise + * @param node the starting node + * @return an Optional containing the ModulePartNode if found, otherwise empty */ - public static boolean hasWeakParameterSettings(SimpleNameReferenceNode varRef, BiPredicate checker) { - String varName = varRef.name().text(); - return hasWeakParameterInScope(varRef.parent(), varName, checker); + public static Optional getModulePartNode(Node node) { + Node parent = node.parent(); + return switch (parent) { + case null -> Optional.empty(); + case ModulePartNode modulePartNode -> Optional.of(modulePartNode); + default -> getModulePartNode(parent); + }; } /** - * Checks if a variable with the given name has weak parameter values within a - * specific scope. + * Retrieves a map of module-level variable names to their initializer expressions + * from the given ModulePartNode. * - * @param startNode the node to start searching from - * @param varName the name of the variable - * @param checker predicate to check if a statement declares a variable with a - * weak value - * @return true if a weak parameter value is found, false otherwise + * @param modulePartNode the module part node + * @return a map where keys are variable names and values are their initializer expressions */ - public static boolean hasWeakParameterInScope(Node startNode, String varName, BiPredicate checker) { - Node current = startNode; - while (current != null) { - if (current instanceof FunctionBodyBlockNode functionBodyBlock) { - if (checkStatementsForWeakParameter(functionBodyBlock.statements(), varName, checker)) { - return true; + public static Map getModuleLevelVarExpressions(ModulePartNode modulePartNode) { + Map varExpressions = new HashMap<>(); + for (ModuleMemberDeclarationNode member : modulePartNode.members()) { + if (member instanceof ModuleVariableDeclarationNode variableDeclarationNode) { + BindingPatternNode bindingPatternNode = variableDeclarationNode.typedBindingPattern().bindingPattern(); + if (variableDeclarationNode.initializer().isEmpty() || + !(bindingPatternNode instanceof CaptureBindingPatternNode captureBindingPattern)) { + continue; } - } else if (current instanceof ModulePartNode modulePart - && checkModuleMembersForWeakParameter(modulePart.members(), varName, checker)) { - return true; + String varName = captureBindingPattern.variableName().text(); + varName = unescapeIdentifier(varName); + ExpressionNode initializer = variableDeclarationNode.initializer().get(); + varExpressions.put(varName, initializer); } - - current = current.parent(); } - return false; + return varExpressions; } /** - * Checks a list of statements for a weak parameter value. + * Adds variable declarations and assignments from the given block node + * to the provided map until the specified statement node is reached. * - * @param statements the statements to check - * @param varName the name of the variable - * @param checker predicate to check if a statement declares a variable with - * a weak value - * @return true if a weak parameter value is found, false otherwise + * @param blockNode the block node (FunctionBodyBlockNode or BlockStatementNode) + * @param statementNode the statement node to stop at + * @param varExpressions the map to store variable names and their expressions */ - public static boolean checkStatementsForWeakParameter(NodeList statements, String varName, - BiPredicate checker) { - for (StatementNode stmt : statements) { - if (checker.test(stmt, varName)) { - return true; - } + public static void addVariableDeclarationsUntil(Node blockNode, StatementNode statementNode, + Map varExpressions) { + NodeList statements; + if (blockNode instanceof FunctionBodyBlockNode functionBody) { + statements = functionBody.statements(); + } else if (blockNode instanceof BlockStatementNode blockStatementNode) { + statements = blockStatementNode.statements(); + } else { + // Any other node type is unsupported + throw new IllegalArgumentException("Unsupported block node type: " + blockNode.kind()); } - return false; + addVarExpressionsUtil(statements, statementNode, varExpressions); } /** - * Checks a list of module members for a weak parameter value. + * Utility method to add variable declarations and assignments from a list of statements + * to the provided map until the specified target statement is reached. * - * @param members the module members to check - * @param varName the name of the variable - * @param checker predicate to check if a statement declares a variable with a - * weak value - * @return true if a weak parameter value is found, false otherwise + * @param statements the list of statements + * @param targetStatement the target statement to stop at + * @param varExpressions the map to store variable names and their expressions */ - public static boolean checkModuleMembersForWeakParameter(NodeList members, - String varName, - BiPredicate checker) { - for (ModuleMemberDeclarationNode member : members) { - if (checker.test(member, varName)) { - return true; + public static void addVarExpressionsUtil(NodeList statements, StatementNode targetStatement, + Map varExpressions) { + for (StatementNode statement : statements) { + // If we find any block nodes in the middle we cannot verify the variable declarations or assignments + // since they may be changed within those blocks. It will become complex if there are conditional blocks + // etc. So we stop the analysis at that point and remove all collected variable expressions. + if (isBlockStatementNode(statement)) { + // Clean the collected variable expressions as we cannot guarantee their validity beyond this point + varExpressions.clear(); + break; } - } - return false; - } - - /** - * Checks if the given expression uses unsecure random number generation. - * This includes checking for random:createIntInRange() calls with various - * prefixes. - * - * @param expression the expression to check - * @param randomPrefixes set of prefixes used for the random module - * @return true if the expression uses unsecure random, false otherwise - */ - public static boolean usesUnsecureRandom(ExpressionNode expression, Set randomPrefixes) { - if (expression instanceof SimpleNameReferenceNode varRef) { - return hasUnsecureRandomInVariableDeclaration(varRef, randomPrefixes); - } - return false; - } - /** - * Checks if the given variable reference refers to a variable that uses - * unsecure random. - * - * @param varRef the variable reference - * @param randomPrefixes set of prefixes used for the random module - * @return true if the variable uses unsecure random, false otherwise - */ - private static boolean hasUnsecureRandomInVariableDeclaration(SimpleNameReferenceNode varRef, - Set randomPrefixes) { - String varName = varRef.name().text(); - return hasUnsecureRandomInScope(varRef.parent(), varName, randomPrefixes); - } + if (statement.equals(targetStatement)) { + break; + } - /** - * Checks if a variable with the given name uses unsecure random within a - * specific scope. - * - * @param startNode the node to start searching from - * @param varName the name of the variable - * @param randomPrefixes set of prefixes used for the random module - * @return true if unsecure random usage is found, false otherwise - */ - private static boolean hasUnsecureRandomInScope(Node startNode, String varName, Set randomPrefixes) { - Node current = startNode; - while (current != null) { - if (current instanceof FunctionBodyBlockNode functionBodyBlock) { - if (checkStatementsForUnsecureRandom(functionBodyBlock.statements(), varName, randomPrefixes)) { - return true; + if (statement instanceof AssignmentStatementNode assignmentNode) { + Node varRef = assignmentNode.varRef(); + if (!(varRef instanceof IdentifierToken variableNameIdentifier)) { + continue; } - } else if (current instanceof ModulePartNode modulePart - && checkModuleMembersForUnsecureRandom(modulePart.members(), varName, randomPrefixes)) { - return true; + String varName = variableNameIdentifier.text(); + varName = unescapeIdentifier(varName); + ExpressionNode expression = assignmentNode.expression(); + varExpressions.put(varName, expression); + } else if (statement instanceof VariableDeclarationNode variableDeclarationNode) { + BindingPatternNode bindingPatternNode = variableDeclarationNode.typedBindingPattern().bindingPattern(); + // Only supporting capture binding patterns for variable declarations + if (variableDeclarationNode.initializer().isEmpty() || + !(bindingPatternNode instanceof CaptureBindingPatternNode captureBindingPattern)) { + break; + } + String varName = captureBindingPattern.variableName().text(); + varName = unescapeIdentifier(varName); + ExpressionNode initializer = variableDeclarationNode.initializer().get(); + varExpressions.put(varName, initializer); } - - current = current.parent(); } - return false; } /** - * Checks a list of statements for unsecure random usage. + * Checks if the given statement node is a block statement or not. * - * @param statements the statements to check - * @param varName the name of the variable - * @param randomPrefixes set of prefixes used for the random module - * @return true if unsecure random usage is found, false otherwise + * @param statement the statement node to check + * @return true if the statement is a block statement, false otherwise */ - private static boolean checkStatementsForUnsecureRandom(NodeList statements, String varName, - Set randomPrefixes) { - for (StatementNode stmt : statements) { - if (containsUnsecureRandomUsage(stmt.toSourceCode(), varName, randomPrefixes)) { - return true; - } - } - return false; + public static boolean isBlockStatementNode(StatementNode statement) { + SyntaxKind kind = statement.kind(); + return kind.equals(SyntaxKind.BLOCK_STATEMENT) || kind.equals(SyntaxKind.DO_STATEMENT) + || kind.equals(SyntaxKind.FORK_STATEMENT) || kind.equals(SyntaxKind.IF_ELSE_STATEMENT) + || kind.equals(SyntaxKind.LOCK_STATEMENT) || kind.equals(SyntaxKind.MATCH_STATEMENT) + || kind.equals(SyntaxKind.FOREACH_STATEMENT) || kind.equals(SyntaxKind.WHILE_STATEMENT) + || kind.equals(SyntaxKind.TRANSACTION_STATEMENT) || kind.equals(SyntaxKind.RETRY_STATEMENT); } /** - * Checks a list of module members for unsecure random usage. + * Retrieves the string value of a parameter from the function context. * - * @param members the module members to check - * @param varName the name of the variable - * @param randomPrefixes set of prefixes used for the random module - * @return true if unsecure random usage is found, false otherwise + * @param key the parameter name + * @param context the function context + * @return an Optional containing the string value if found, otherwise empty */ - private static boolean checkModuleMembersForUnsecureRandom(NodeList members, - String varName, Set randomPrefixes) { - for (ModuleMemberDeclarationNode member : members) { - if (containsUnsecureRandomUsage(member.toSourceCode(), varName, randomPrefixes)) { - return true; - } + public static Optional getStringValue(String key, FunctionContext context) { + Optional valueExprOpt = context.getParamExpression(key); + if (valueExprOpt.isEmpty()) { + return Optional.empty(); } - return false; - } - /** - * Checks if the source code contains unsecure random usage for a specific - * variable. - * - * @param sourceCode the source code to check - * @param varName the name of the variable - * @param randomPrefixes set of prefixes used for the random module - * @return true if unsecure random usage is found, false otherwise - */ - private static boolean containsUnsecureRandomUsage(String sourceCode, String varName, Set randomPrefixes) { - for (String prefix : randomPrefixes) { - String randomCallPattern = prefix + ":" + CREATE_INT_IN_RANGE; - if (sourceCode.contains(varName) && sourceCode.contains(randomCallPattern)) { - return true; + ExpressionNode valueExpr = valueExprOpt.get(); + if (valueExpr.kind().equals(SyntaxKind.STRING_LITERAL)) { + // String literal values + String stringValue = ((BasicLiteralNode) valueExpr).literalToken().text(); + // Remove the leading and trailing double quotes + stringValue = stringValue.substring(1, stringValue.length() - 1); + return Optional.of(stringValue); + } else if (valueExpr instanceof NameReferenceNode refNode) { + // Checking for constant values + Optional refSymbol = context.semanticModel().symbol(refNode); + if (refSymbol.isPresent() && refSymbol.get() instanceof ConstantSymbol constantRef) { + if (constantRef.constValue() instanceof ConstantValue constantValue && + constantValue.value() instanceof String constString) { + return Optional.of(constString); + } } } - return false; - } - - /** - * Retrieves the Document corresponding to the given module and document ID. - * - * @param module the module - * @param documentId the document ID - * @return the Document for the given module and document ID - */ - public static Document getDocument(Module module, DocumentId documentId) { - return module.document(documentId); + return Optional.empty(); } } diff --git a/compiler-plugin/src/main/java/io/ballerina/stdlib/crypto/compiler/staticcodeanalyzer/CryptoCipherAlgorithmAnalyzer.java b/compiler-plugin/src/main/java/io/ballerina/stdlib/crypto/compiler/staticcodeanalyzer/CryptoCipherAlgorithmAnalyzer.java index a963e4c7..b68fdcf2 100644 --- a/compiler-plugin/src/main/java/io/ballerina/stdlib/crypto/compiler/staticcodeanalyzer/CryptoCipherAlgorithmAnalyzer.java +++ b/compiler-plugin/src/main/java/io/ballerina/stdlib/crypto/compiler/staticcodeanalyzer/CryptoCipherAlgorithmAnalyzer.java @@ -18,42 +18,30 @@ package io.ballerina.stdlib.crypto.compiler.staticcodeanalyzer; -import io.ballerina.compiler.syntax.tree.ExpressionNode; +import io.ballerina.compiler.api.SemanticModel; +import io.ballerina.compiler.api.symbols.FunctionSymbol; import io.ballerina.compiler.syntax.tree.FunctionCallExpressionNode; -import io.ballerina.compiler.syntax.tree.ImportOrgNameNode; -import io.ballerina.compiler.syntax.tree.ImportPrefixNode; -import io.ballerina.compiler.syntax.tree.ModulePartNode; -import io.ballerina.compiler.syntax.tree.NamedArgumentNode; -import io.ballerina.compiler.syntax.tree.Node; -import io.ballerina.compiler.syntax.tree.PositionalArgumentNode; -import io.ballerina.compiler.syntax.tree.QualifiedNameReferenceNode; +import io.ballerina.compiler.syntax.tree.SyntaxKind; import io.ballerina.projects.Document; import io.ballerina.projects.plugins.AnalysisTask; import io.ballerina.projects.plugins.SyntaxNodeAnalysisContext; import io.ballerina.scan.Reporter; -import java.util.HashSet; -import java.util.Set; +import java.util.Optional; -import static io.ballerina.stdlib.crypto.compiler.staticcodeanalyzer.CryptoRule.AVOID_FAST_HASH_ALGORITHMS; -import static io.ballerina.stdlib.crypto.compiler.staticcodeanalyzer.CryptoRule.AVOID_REUSING_COUNTER_MODE_VECTORS; +import static io.ballerina.stdlib.crypto.compiler.staticcodeanalyzer.CryptoAnalyzerUtils.getCryptoFunctionSymbol; /** * Analyzer to detect the usage of weak cipher algorithms and insecure practices in the Ballerina crypto module. */ public class CryptoCipherAlgorithmAnalyzer implements AnalysisTask { + private final Reporter reporter; - private static final String BALLERINA_ORG = "ballerina"; - private static final String CRYPTO = "crypto"; - private static final String IV = "iv"; - private static final String RANDOM = "random"; - private final Set cryptoPrefixes = new HashSet<>(); - private final Set randomPrefixes = new HashSet<>(); + private final CryptoFunctionRulesEngine rulesEngine; public CryptoCipherAlgorithmAnalyzer(Reporter reporter) { this.reporter = reporter; - this.cryptoPrefixes.add(CRYPTO); - this.randomPrefixes.add(RANDOM); + this.rulesEngine = new CryptoFunctionRulesEngine(); } /** @@ -63,263 +51,21 @@ public CryptoCipherAlgorithmAnalyzer(Reporter reporter) { */ @Override public void perform(SyntaxNodeAnalysisContext context) { - analyzeImports(context); - FunctionCallExpressionNode functionCall = (FunctionCallExpressionNode) context.node(); - - if (!(functionCall.functionName() instanceof QualifiedNameReferenceNode qualifiedName)) { - return; - } - - String modulePrefix = qualifiedName.modulePrefix().text(); - - if (!cryptoPrefixes.contains(modulePrefix)) { - return; - } - - String functionName = qualifiedName.identifier().text(); - - if (CryptoAnalyzerUtils.isWeakCipherFunction(functionName)) { - report(context, CryptoRule.AVOID_WEAK_CIPHER_ALGORITHMS.getId()); - } - - if (CryptoAnalyzerUtils.requiresSecureIV(functionName)) { - checkHardcodedIVUsage(functionCall, context); - checkUnsecureRandomUsage(functionCall, context); - } - - if (CryptoAnalyzerUtils.HASH_BCRYPT.equals(functionName)) { - checkWeakBcryptUsage(functionCall, context); - } else if (CryptoAnalyzerUtils.HASH_ARGON2.equals(functionName)) { - checkWeakArgon2Usage(functionCall, context); - } else if (CryptoAnalyzerUtils.HASH_PBKDF2.equals(functionName)) { - checkWeakPbkdf2Usage(functionCall, context); - } - } - - /** - * Checks if the bcrypt hash function is used with weak parameters. - * - * @param functionCall the function call node - * @param context the syntax node analysis context - */ - private void checkWeakBcryptUsage(FunctionCallExpressionNode functionCall, SyntaxNodeAnalysisContext context) { - if (functionCall.arguments().stream().count() < 2) { - return; - } - - Node workFactor = functionCall.arguments().get(1); - - if (workFactor instanceof PositionalArgumentNode positional) { - ExpressionNode expr = positional.expression(); - if (CryptoAnalyzerUtils.isWeakBcryptParameter(expr)) { - report(context, AVOID_FAST_HASH_ALGORITHMS.getId()); - } - } else if (workFactor instanceof NamedArgumentNode named) { - ExpressionNode expr = named.expression(); - if (CryptoAnalyzerUtils.isWeakBcryptParameter(expr)) { - report(context, AVOID_FAST_HASH_ALGORITHMS.getId()); - } - } - } - - /** - * Checks if the Argon2 hash function is used with weak parameters. - * - * @param functionCall the function call node - * @param context the syntax node analysis context - */ - private void checkWeakArgon2Usage(FunctionCallExpressionNode functionCall, SyntaxNodeAnalysisContext context) { - int argsCount = (int) functionCall.arguments().stream().count(); - if (argsCount < 2) { - return; - } - - boolean hasWeakParameters = false; - - for (Node arg : functionCall.arguments()) { - if (arg instanceof NamedArgumentNode named) { - String paramName = named.argumentName().name().text(); - ExpressionNode expr = named.expression(); - - if (CryptoAnalyzerUtils.ITERATIONS.equals(paramName)) { - hasWeakParameters |= CryptoAnalyzerUtils.isWeakArgon2Parameter(expr, - CryptoAnalyzerUtils.ArgonParameter.ITERATIONS); - } else if (CryptoAnalyzerUtils.MEMORY.equals(paramName)) { - hasWeakParameters |= CryptoAnalyzerUtils.isWeakArgon2Parameter(expr, - CryptoAnalyzerUtils.ArgonParameter.MEMORY); - } else if (CryptoAnalyzerUtils.PARALLELISM.equals(paramName)) { - hasWeakParameters |= CryptoAnalyzerUtils.isWeakArgon2Parameter(expr, - CryptoAnalyzerUtils.ArgonParameter.PARALLELISM); - } - } - } - - // Check for positional arguments - if (functionCall.arguments().get(1) instanceof PositionalArgumentNode iterationsArg) { - hasWeakParameters |= CryptoAnalyzerUtils.isWeakArgon2Parameter(iterationsArg.expression(), - CryptoAnalyzerUtils.ArgonParameter.ITERATIONS); - } - - if (argsCount >= 3 && functionCall.arguments().get(2) instanceof PositionalArgumentNode memoryArg) { - hasWeakParameters |= CryptoAnalyzerUtils.isWeakArgon2Parameter(memoryArg.expression(), - CryptoAnalyzerUtils.ArgonParameter.MEMORY); - } - - if (argsCount >= 4 && functionCall.arguments().get(3) instanceof PositionalArgumentNode parallelismArg) { - hasWeakParameters |= CryptoAnalyzerUtils.isWeakArgon2Parameter(parallelismArg.expression(), - CryptoAnalyzerUtils.ArgonParameter.PARALLELISM); - } - - if (hasWeakParameters) { - report(context, AVOID_FAST_HASH_ALGORITHMS.getId()); - } - } - - /** - * Checks if the PBKDF2 hash function is used with weak parameters. - * - * @param functionCall the function call node - * @param context the syntax node analysis context - */ - private void checkWeakPbkdf2Usage(FunctionCallExpressionNode functionCall, SyntaxNodeAnalysisContext context) { - int argsCount = (int) functionCall.arguments().stream().count(); - if (argsCount < 2) { - report(context, AVOID_FAST_HASH_ALGORITHMS.getId()); - return; - } - - boolean hasWeakParameters = false; - - for (Node arg : functionCall.arguments()) { - if (arg instanceof NamedArgumentNode named) { - String paramName = named.argumentName().name().text(); - if (CryptoAnalyzerUtils.ITERATIONS.equals(paramName)) { - hasWeakParameters |= CryptoAnalyzerUtils.isWeakPbkdf2Parameter(named.expression()); - } - } - } - - if (functionCall.arguments().get(1) instanceof PositionalArgumentNode iterationsArg) { - hasWeakParameters |= CryptoAnalyzerUtils.isWeakPbkdf2Parameter(iterationsArg.expression()); - } - - if (hasWeakParameters) { - report(context, AVOID_FAST_HASH_ALGORITHMS.getId()); - } - } - - /** - * Checks if the AES-GCM/AES-ECB functions use hardcoded initialization vectors. - * For encryptAesGcm(input, key, iv, padding, tagSize), the third parameter (iv) - * is checked. - * - * @param functionCall the function call node - * @param context the syntax node analysis context - */ - private void checkHardcodedIVUsage(FunctionCallExpressionNode functionCall, SyntaxNodeAnalysisContext context) { - if (functionCall.arguments().stream().count() < 3) { + SemanticModel semanticModel = context.semanticModel(); + Document document = CryptoAnalyzerUtils.getDocument(context.currentPackage().module(context.moduleId()), + context.documentId()); + if (!(functionCall.functionName().kind().equals(SyntaxKind.QUALIFIED_NAME_REFERENCE))) { return; } - Node ivArgument = functionCall.arguments().get(2); - - if (ivArgument instanceof PositionalArgumentNode positional) { - ExpressionNode expr = positional.expression(); - if (CryptoAnalyzerUtils.isHardcodedIV(expr, context)) { - report(context, AVOID_REUSING_COUNTER_MODE_VECTORS.getId()); - } - } else if (ivArgument instanceof NamedArgumentNode named) { - // Check for named arguments - String paramName = named.argumentName().name().text(); - if (IV.equals(paramName)) { - ExpressionNode expr = named.expression(); - if (CryptoAnalyzerUtils.isHardcodedIV(expr, context)) { - report(context, AVOID_REUSING_COUNTER_MODE_VECTORS.getId()); - } - } - } - } - - /** - * Checks if the AES-GCM/AES-ECB functions use unsecure random number generation for - * initialization vectors. - * For encryptAes(input, key, iv, padding, tagSize), the third parameter (iv) - * is checked for usage of random:createIntInRange(). - * - * @param functionCall the function call node - * @param context the syntax node analysis context - */ - private void checkUnsecureRandomUsage(FunctionCallExpressionNode functionCall, SyntaxNodeAnalysisContext context) { - if (functionCall.arguments().stream().count() < 3) { + Optional functionSymbolOpt = getCryptoFunctionSymbol(functionCall, semanticModel); + if (functionSymbolOpt.isEmpty()) { return; } - Node ivArgument = functionCall.arguments().get(2); - - if (ivArgument instanceof PositionalArgumentNode positional) { - ExpressionNode expr = positional.expression(); - if (CryptoAnalyzerUtils.usesUnsecureRandom(expr, randomPrefixes)) { - report(context, CryptoRule.AVOID_USING_UNSECURE_RANDOM_NUMBER_GENERATORS.getId()); - } - } else if (ivArgument instanceof NamedArgumentNode named) { - String paramName = named.argumentName().name().text(); - if ("iv".equals(paramName)) { - ExpressionNode expr = named.expression(); - if (CryptoAnalyzerUtils.usesUnsecureRandom(expr, randomPrefixes)) { - report(context, CryptoRule.AVOID_USING_UNSECURE_RANDOM_NUMBER_GENERATORS.getId()); - } - } - } - } - - /** - * Reports an issue for the given context and rule ID. - * - * @param context the syntax node analysis context - * @param ruleId the ID of the rule to report - */ - private void report(SyntaxNodeAnalysisContext context, int ruleId) { - reporter.reportIssue( - CryptoAnalyzerUtils.getDocument(context.currentPackage().module(context.moduleId()), - context.documentId()), - context.node().location(), - ruleId); - } - - /** - * Analyzes imports to identify all prefixes used for the crypto module. - * - * @param context the syntax node analysis context - */ - private void analyzeImports(SyntaxNodeAnalysisContext context) { - Document document = CryptoAnalyzerUtils.getDocument(context.currentPackage().module(context.moduleId()), - context.documentId()); - - if (document.syntaxTree().rootNode() instanceof ModulePartNode modulePartNode) { - modulePartNode.imports().forEach(importDeclarationNode -> { - ImportOrgNameNode importOrgNameNode = importDeclarationNode.orgName().orElse(null); - - if (importOrgNameNode != null && BALLERINA_ORG.equals(importOrgNameNode.orgName().text())) { - if (importDeclarationNode.moduleName().stream() - .anyMatch(moduleNameNode -> CRYPTO.equals(moduleNameNode.text()))) { - - ImportPrefixNode importPrefixNode = importDeclarationNode.prefix().orElse(null); - String prefix = importPrefixNode != null ? importPrefixNode.prefix().text() : CRYPTO; - - cryptoPrefixes.add(prefix); - } - - if (importDeclarationNode.moduleName().stream() - .anyMatch(moduleNameNode -> RANDOM.equals(moduleNameNode.text()))) { - - ImportPrefixNode importPrefixNode = importDeclarationNode.prefix().orElse(null); - String prefix = importPrefixNode != null ? importPrefixNode.prefix().text() : RANDOM; - - randomPrefixes.add(prefix); - } - } - }); - } + FunctionContext functionContext = FunctionContext.getInstance(semanticModel, this.reporter, document, + functionCall, functionSymbolOpt.get()); + rulesEngine.executeRules(functionContext); } } diff --git a/compiler-plugin/src/main/java/io/ballerina/stdlib/crypto/compiler/staticcodeanalyzer/CryptoFunctionRulesEngine.java b/compiler-plugin/src/main/java/io/ballerina/stdlib/crypto/compiler/staticcodeanalyzer/CryptoFunctionRulesEngine.java new file mode 100644 index 00000000..f9b46ea8 --- /dev/null +++ b/compiler-plugin/src/main/java/io/ballerina/stdlib/crypto/compiler/staticcodeanalyzer/CryptoFunctionRulesEngine.java @@ -0,0 +1,62 @@ +/* + * Copyright (c) 2025, WSO2 LLC. (http://www.wso2.com). + * + * WSO2 LLC. licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package io.ballerina.stdlib.crypto.compiler.staticcodeanalyzer; + +import io.ballerina.stdlib.crypto.compiler.staticcodeanalyzer.functionrules.AvoidFastHashAlgorithmsRule; +import io.ballerina.stdlib.crypto.compiler.staticcodeanalyzer.functionrules.AvoidReusingCounterModeVectorsRule; +import io.ballerina.stdlib.crypto.compiler.staticcodeanalyzer.functionrules.AvoidWeakCipherAlgorithmsRule; +import io.ballerina.stdlib.crypto.compiler.staticcodeanalyzer.functionrules.CryptoFunctionRule; + +import java.util.ArrayList; +import java.util.List; + +/** + * Engine to execute crypto function rules. + * + * @since 2.9.1 + */ +public class CryptoFunctionRulesEngine { + + private final List rules; + + public CryptoFunctionRulesEngine() { + this.rules = new ArrayList<>(); + initializeDefaultRules(); + } + + public void executeRules(FunctionContext context) { + for (CryptoFunctionRule rule : rules) { + if (rule.isApplicable(context)) { + rule.analyze(context); + } + } + } + + public void addRule(CryptoFunctionRule rule) { + if (rule != null && !rules.contains(rule)) { + rules.add(rule); + } + } + + private void initializeDefaultRules() { + addRule(new AvoidWeakCipherAlgorithmsRule()); + addRule(new AvoidFastHashAlgorithmsRule()); + addRule(new AvoidReusingCounterModeVectorsRule()); + // Add more default rules here as needed + } +} diff --git a/compiler-plugin/src/main/java/io/ballerina/stdlib/crypto/compiler/staticcodeanalyzer/CryptoRule.java b/compiler-plugin/src/main/java/io/ballerina/stdlib/crypto/compiler/staticcodeanalyzer/CryptoRule.java index ca2aa96a..60f0d899 100644 --- a/compiler-plugin/src/main/java/io/ballerina/stdlib/crypto/compiler/staticcodeanalyzer/CryptoRule.java +++ b/compiler-plugin/src/main/java/io/ballerina/stdlib/crypto/compiler/staticcodeanalyzer/CryptoRule.java @@ -29,9 +29,7 @@ public enum CryptoRule { AVOID_FAST_HASH_ALGORITHMS(createRule(2, "Avoid using fast hashing algorithms", VULNERABILITY)), AVOID_REUSING_COUNTER_MODE_VECTORS(createRule(3, - "Avoid reusing counter mode initialization vectors", VULNERABILITY)), - AVOID_USING_UNSECURE_RANDOM_NUMBER_GENERATORS(createRule(4, - "Secure random number generators should not output predictable values", VULNERABILITY)); + "Avoid reusing counter mode initialization vectors", VULNERABILITY)); private final Rule rule; diff --git a/compiler-plugin/src/main/java/io/ballerina/stdlib/crypto/compiler/staticcodeanalyzer/FunctionContext.java b/compiler-plugin/src/main/java/io/ballerina/stdlib/crypto/compiler/staticcodeanalyzer/FunctionContext.java new file mode 100644 index 00000000..9f295bc3 --- /dev/null +++ b/compiler-plugin/src/main/java/io/ballerina/stdlib/crypto/compiler/staticcodeanalyzer/FunctionContext.java @@ -0,0 +1,230 @@ +/* + * Copyright (c) 2025, WSO2 LLC. (http://www.wso2.com). + * + * WSO2 LLC. licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package io.ballerina.stdlib.crypto.compiler.staticcodeanalyzer; + +import io.ballerina.compiler.api.SemanticModel; +import io.ballerina.compiler.api.symbols.FunctionSymbol; +import io.ballerina.compiler.api.symbols.ParameterSymbol; +import io.ballerina.compiler.syntax.tree.ExpressionNode; +import io.ballerina.compiler.syntax.tree.FunctionArgumentNode; +import io.ballerina.compiler.syntax.tree.FunctionCallExpressionNode; +import io.ballerina.compiler.syntax.tree.ModulePartNode; +import io.ballerina.compiler.syntax.tree.Node; +import io.ballerina.compiler.syntax.tree.SeparatedNodeList; +import io.ballerina.compiler.syntax.tree.SimpleNameReferenceNode; +import io.ballerina.compiler.syntax.tree.StatementNode; +import io.ballerina.projects.Document; +import io.ballerina.scan.Reporter; +import io.ballerina.tools.diagnostics.Location; + +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Optional; + +import static io.ballerina.stdlib.crypto.compiler.staticcodeanalyzer.CryptoAnalyzerUtils.addVariableDeclarationsUntil; +import static io.ballerina.stdlib.crypto.compiler.staticcodeanalyzer.CryptoAnalyzerUtils.getModuleLevelVarExpressions; +import static io.ballerina.stdlib.crypto.compiler.staticcodeanalyzer.CryptoAnalyzerUtils.getModulePartNode; +import static io.ballerina.stdlib.crypto.compiler.staticcodeanalyzer.CryptoAnalyzerUtils.getParamExpressions; +import static io.ballerina.stdlib.crypto.compiler.staticcodeanalyzer.CryptoAnalyzerUtils.getParentBlockNode; +import static io.ballerina.stdlib.crypto.compiler.staticcodeanalyzer.CryptoAnalyzerUtils.getStatementNode; +import static io.ballerina.stdlib.crypto.compiler.staticcodeanalyzer.CryptoAnalyzerUtils.unescapeIdentifier; + +/** + * Represents the context of a function being analyzed. + * + * @since 2.9.1 + */ +public class FunctionContext { + private final SemanticModel semanticModel; + private final Reporter reporter; + private final Document document; + private final String functionName; + private final Location functionLocation; + private Map paramExpressions = Map.of(); + private Map varExpressions = Map.of(); + + /** + * Creates a FunctionContext instance for the given function call and symbol. + * + * @param semanticModel The semantic model + * @param reporter The reporter for diagnostics + * @param document The document containing the function call + * @param functionCall The function call expression node + * @param functionSymbol The function symbol + * @return A FunctionContext instance + */ + public static FunctionContext getInstance(SemanticModel semanticModel, Reporter reporter, Document document, + FunctionCallExpressionNode functionCall, + FunctionSymbol functionSymbol) { + Location location = functionCall.location(); + SeparatedNodeList arguments = functionCall.arguments(); + Optional functionNameOpt = functionSymbol.getName(); + if (functionNameOpt.isEmpty()) { + // This should not happen as function symbols always have name in this context + throw new IllegalStateException("Function name is not available for the function symbol"); + } + + String functionName = functionNameOpt.get(); + Optional> params = functionSymbol.typeDescriptor().params(); + // Create a default FunctionContext in case of missing parameters or arguments + FunctionContext defaultFunctionContext = new FunctionContext(semanticModel, reporter, document, functionName, + location); + if (params.isEmpty() || arguments.isEmpty()) { + return defaultFunctionContext; + } + + Map paramExpressions = getParamExpressions(params.get(), arguments); + + Optional statementNode = getStatementNode(functionCall); + if (statementNode.isEmpty()) { + return defaultFunctionContext; + } + + // A map is used to hold variable expressions in global and function block scope. + // The key is the variable name and the value is the expression node assigned to it. + // Processing happens in an order using the NodeList. Hence, expressions will be overridden + // if there are reassignments with the same variable name + Map varExpressions = new HashMap<>(); + + // Collect module level variable expressions + Optional modulePartNode = getModulePartNode(statementNode.get()); + if (modulePartNode.isPresent()) { + varExpressions = getModuleLevelVarExpressions(modulePartNode.get()); + } + + Optional functionBodyOpt = getParentBlockNode(statementNode.get()); + if (functionBodyOpt.isEmpty()) { + return defaultFunctionContext; + } + + // Add variable declarations up to the function call statement + addVariableDeclarationsUntil(functionBodyOpt.get(), statementNode.get(), varExpressions); + return new FunctionContext(semanticModel, reporter, document, functionName, location, paramExpressions, + varExpressions); + } + + // Private constructor to enforce the use of the instance method + private FunctionContext(SemanticModel semanticModel, Reporter reporter, Document document, String functionName, + Location functionLocation) { + this.semanticModel = semanticModel; + this.reporter = reporter; + this.document = document; + this.functionName = functionName; + this.functionLocation = functionLocation; + } + + // Private constructor to enforce the use of the instance method + private FunctionContext(SemanticModel semanticModel, Reporter reporter, Document document, String functionName, + Location functionLocation, Map paramExpressions, + Map varExpressions) { + this(semanticModel, reporter, document, functionName, functionLocation); + this.paramExpressions = paramExpressions; + this.varExpressions = varExpressions; + } + + /** + * Returns the semantic model. + * + * @return the semantic model + */ + public SemanticModel semanticModel() { + return semanticModel; + } + + /** + * Returns the reporter. + * + * @return the reporter + */ + public Reporter reporter() { + return reporter; + } + + /** + * Returns the document. + * + * @return the document + */ + public Document document() { + return document; + } + + /** + * Returns the function name. + * + * @return the function name + */ + public String functionName() { + return functionName; + } + + /** + * Returns the function location. + * + * @return the function location + */ + public Location functionLocation() { + return functionLocation; + } + + /** + * Retrieves the expression node for a given parameter name. + * If the parameter expression is a simple name reference, it resolves + * the reference to get the actual expression. + * + * @param paramName The name of the parameter + * @return An Optional containing the expression node if found, otherwise empty + */ + public Optional getParamExpression(String paramName) { + paramName = unescapeIdentifier(paramName); + if (!paramExpressions.containsKey(paramName)) { + return Optional.empty(); + } + ExpressionNode paramExpr = paramExpressions.get(paramName); + if (paramExpr instanceof SimpleNameReferenceNode simpleNameRef) { + String varName = simpleNameRef.name().text(); + // Retrying to get the expression from variable expressions + // collected with the block scope and global scope + Optional varExprOpt = getVarExpression(varName); + return varExprOpt.isPresent() ? varExprOpt : Optional.of(paramExpr); + } + return Optional.of(paramExpr); + } + + /** + * Retrieves the expression node for a given variable name. + * If the variable expression is a simple name reference, it resolves + * the reference to get the actual expression. + * + * @param varName The name of the variable + * @return An Optional containing the expression node if found, otherwise empty + */ + private Optional getVarExpression(String varName) { + varName = unescapeIdentifier(varName); + if (!varExpressions.containsKey(varName)) { + return Optional.empty(); + } + ExpressionNode varExpr = varExpressions.get(varName); + if (varExpr instanceof SimpleNameReferenceNode simpleNameRef) { + String innerVarName = simpleNameRef.name().text(); + return getVarExpression(innerVarName); + } + return Optional.of(varExpr); + } +} diff --git a/compiler-plugin/src/main/java/io/ballerina/stdlib/crypto/compiler/staticcodeanalyzer/functionrules/AvoidFastHashAlgorithmsRule.java b/compiler-plugin/src/main/java/io/ballerina/stdlib/crypto/compiler/staticcodeanalyzer/functionrules/AvoidFastHashAlgorithmsRule.java new file mode 100644 index 00000000..4eb17c60 --- /dev/null +++ b/compiler-plugin/src/main/java/io/ballerina/stdlib/crypto/compiler/staticcodeanalyzer/functionrules/AvoidFastHashAlgorithmsRule.java @@ -0,0 +1,164 @@ +/* + * Copyright (c) 2025, WSO2 LLC. (http://www.wso2.com). + * + * WSO2 LLC. licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package io.ballerina.stdlib.crypto.compiler.staticcodeanalyzer.functionrules; + +import io.ballerina.compiler.api.SemanticModel; +import io.ballerina.compiler.api.symbols.ConstantSymbol; +import io.ballerina.compiler.api.symbols.Symbol; +import io.ballerina.compiler.syntax.tree.BasicLiteralNode; +import io.ballerina.compiler.syntax.tree.ExpressionNode; +import io.ballerina.compiler.syntax.tree.NameReferenceNode; +import io.ballerina.compiler.syntax.tree.SyntaxKind; +import io.ballerina.stdlib.crypto.compiler.staticcodeanalyzer.FunctionContext; + +import java.util.Optional; + +import static io.ballerina.stdlib.crypto.compiler.staticcodeanalyzer.CryptoAnalyzerUtils.getStringValue; +import static io.ballerina.stdlib.crypto.compiler.staticcodeanalyzer.CryptoRule.AVOID_FAST_HASH_ALGORITHMS; + +/** + * Rule to avoid fast hash algorithms. + * Analyzes the usage of bcrypt, argon2, and pbkdf2 hashing functions with weak parameters. + * + * @since 2.9.1 + */ +public class AvoidFastHashAlgorithmsRule implements CryptoFunctionRule { + + public static final String HASH_BCRYPT = "hashBcrypt"; + public static final String HASH_ARGON2 = "hashArgon2"; + public static final String HASH_PBKDF2 = "hashPbkdf2"; + public static final String WORK_FACTOR = "workFactor"; + public static final String ITERATIONS = "iterations"; + public static final String MEMORY = "memory"; + public static final String PARALLELISM = "parallelism"; + public static final String ALGORITHM = "algorithm"; + public static final String HMAC_SHA1 = "SHA1"; + public static final String HMAC_SHA256 = "SHA256"; + public static final String HMAC_SHA512 = "SHA512"; + + public static final int BCRYPT_RECOMMENDED_WORK_FACTOR = 10; + public static final int ARGON2_RECOMMENDED_ITERATIONS = 2; + public static final int ARGON2_RECOMMENDED_MEMORY = 19456; + public static final int ARGON2_RECOMMENDED_PARALLELISM = 1; + public static final int PBKDF2_RECOMMENDED_ITERATIONS_FOR_SHA1 = 1300000; + public static final int PBKDF2_RECOMMENDED_ITERATIONS_FOR_SHA256 = 600000; + public static final int PBKDF2_RECOMMENDED_ITERATIONS_FOR_SHA512 = 210000; + + @Override + public void analyze(FunctionContext context) { + if (isBCryptWithLowWorkFactor(context) || isArgon2WithWeakParams(context) + || isPBKDF2WithLowIterations(context)) { + context.reporter().reportIssue(context.document(), context.functionLocation(), getRuleId()); + } + } + + @Override + public int getRuleId() { + return AVOID_FAST_HASH_ALGORITHMS.getId(); + } + + @Override + public boolean isApplicable(FunctionContext context) { + String functionName = context.functionName(); + return functionName.equals(HASH_BCRYPT) || functionName.equals(HASH_ARGON2) + || functionName.equals(HASH_PBKDF2); + } + + private boolean isBCryptWithLowWorkFactor(FunctionContext context) { + if (!context.functionName().equals(HASH_BCRYPT)) { + return false; + } + SemanticModel semanticModel = context.semanticModel(); + Optional workFactorOpt = context.getParamExpression(WORK_FACTOR); + // If work factor is not provided, default is 12 which is considered secure + return workFactorOpt + .filter(expr -> hasLowerIntegerValue(expr, BCRYPT_RECOMMENDED_WORK_FACTOR, semanticModel)) + .isPresent(); + + } + + private boolean isArgon2WithWeakParams(FunctionContext context) { + if (!context.functionName().equals(HASH_ARGON2)) { + return false; + } + SemanticModel semanticModel = context.semanticModel(); + // Check if any parameter is below the recommended threshold + return isArgon2ParamBelowThreshold(context, ITERATIONS, ARGON2_RECOMMENDED_ITERATIONS, semanticModel) || + isArgon2ParamBelowThreshold(context, MEMORY, ARGON2_RECOMMENDED_MEMORY, semanticModel) || + isArgon2ParamBelowThreshold(context, PARALLELISM, ARGON2_RECOMMENDED_PARALLELISM, semanticModel); + } + + private boolean isArgon2ParamBelowThreshold(FunctionContext context, String paramName, + int recommendedValue, SemanticModel semanticModel) { + Optional paramOpt = context.getParamExpression(paramName); + // If parameter is not provided, defaults are used which are considered secure + // Default iterations: 3, Default memory: 65536, Default parallelism: 4 + return paramOpt + .filter(expr -> hasLowerIntegerValue(expr, recommendedValue, semanticModel)) + .isPresent(); + } + + private boolean isPBKDF2WithLowIterations(FunctionContext context) { + if (!context.functionName().equals(HASH_PBKDF2)) { + return false; + } + + // If algorithm is not provided, default is HMAC_SHA256 + String algorithm = getStringValue(ALGORITHM, context).orElse(HMAC_SHA256); + + SemanticModel semanticModel = context.semanticModel(); + Optional iterationsOpt = context.getParamExpression(ITERATIONS); + if (iterationsOpt.isEmpty()) { + // Default iterations for is 10000 which is lower than recommended for all algorithms + return true; + } + + int recommendedIterations = getRecommendedIterationsForPBKDF2(algorithm); + return hasLowerIntegerValue(iterationsOpt.get(), recommendedIterations, semanticModel); + } + + private static int getRecommendedIterationsForPBKDF2(String algorithm) { + return switch (algorithm) { + case HMAC_SHA1 -> PBKDF2_RECOMMENDED_ITERATIONS_FOR_SHA1; + case HMAC_SHA256 -> PBKDF2_RECOMMENDED_ITERATIONS_FOR_SHA256; + case HMAC_SHA512 -> PBKDF2_RECOMMENDED_ITERATIONS_FOR_SHA512; + default -> throw new IllegalArgumentException("Unsupported HMAC algorithm: " + algorithm + + " found for PBKDF2 hashing function"); + }; + } + + private static boolean hasLowerIntegerValue(ExpressionNode valueExpr, Integer targetValue, + SemanticModel semanticModel) { + if (valueExpr.kind().equals(SyntaxKind.NUMERIC_LITERAL)) { + String iterationsValue = ((BasicLiteralNode) valueExpr).literalToken().text(); + try { + int iterationsInt = Integer.parseInt(iterationsValue); + return iterationsInt < targetValue; + } catch (NumberFormatException e) { + // Ignore and continue + } + } else if (valueExpr instanceof NameReferenceNode refNode) { + Optional refSymbol = semanticModel.symbol(refNode); + if (refSymbol.isPresent() && refSymbol.get() instanceof ConstantSymbol constantRef) { + return constantRef.constValue() instanceof Integer && + ((Integer) constantRef.constValue()) < targetValue; + } + } + return false; + } +} diff --git a/compiler-plugin/src/main/java/io/ballerina/stdlib/crypto/compiler/staticcodeanalyzer/functionrules/AvoidReusingCounterModeVectorsRule.java b/compiler-plugin/src/main/java/io/ballerina/stdlib/crypto/compiler/staticcodeanalyzer/functionrules/AvoidReusingCounterModeVectorsRule.java new file mode 100644 index 00000000..6b415c3f --- /dev/null +++ b/compiler-plugin/src/main/java/io/ballerina/stdlib/crypto/compiler/staticcodeanalyzer/functionrules/AvoidReusingCounterModeVectorsRule.java @@ -0,0 +1,94 @@ +/* + * Copyright (c) 2025, WSO2 LLC. (http://www.wso2.com). + * + * WSO2 LLC. licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package io.ballerina.stdlib.crypto.compiler.staticcodeanalyzer.functionrules; + +import io.ballerina.compiler.syntax.tree.ExpressionNode; +import io.ballerina.compiler.syntax.tree.ListConstructorExpressionNode; +import io.ballerina.compiler.syntax.tree.MethodCallExpressionNode; +import io.ballerina.compiler.syntax.tree.NameReferenceNode; +import io.ballerina.compiler.syntax.tree.SimpleNameReferenceNode; +import io.ballerina.compiler.syntax.tree.SyntaxKind; +import io.ballerina.stdlib.crypto.compiler.staticcodeanalyzer.FunctionContext; + +import java.util.Optional; + +import static io.ballerina.stdlib.crypto.compiler.staticcodeanalyzer.CryptoRule.AVOID_REUSING_COUNTER_MODE_VECTORS; + +/** + * Rule to avoid reusing initialization vectors (IVs) in counter mode encryption algorithms such as AES-CBC + * and AES-GCM. + * + * @since 2.9.1 + */ +public class AvoidReusingCounterModeVectorsRule implements CryptoFunctionRule { + + public static final String ENCRYPT_AES_CBC = "encryptAesCbc"; + public static final String ENCRYPT_AES_GCM = "encryptAesGcm"; + public static final String INITIALIZATION_VECTOR = "iv"; + public static final String TO_BYTES_METHOD = "toBytes"; + + @Override + public void analyze(FunctionContext context) { + Optional paramExpression = context.getParamExpression(INITIALIZATION_VECTOR); + if (paramExpression.isEmpty()) { + // The IV is a required parameter for these functions, so this case should not occur. + throw new IllegalStateException("Initialization vector parameter is missing for function: " + + context.functionName()); + } + + if (hasHardCodedIV(paramExpression.get())) { + context.reporter().reportIssue(context.document(), context.functionLocation(), getRuleId()); + } + } + + @Override + public int getRuleId() { + return AVOID_REUSING_COUNTER_MODE_VECTORS.getId(); + + } + + @Override + public boolean isApplicable(FunctionContext context) { + String functionName = context.functionName(); + return functionName.equals(ENCRYPT_AES_CBC) || functionName.equals(ENCRYPT_AES_GCM); + } + + private boolean hasHardCodedIV(ExpressionNode ivExpression) { + // Check for list constructor with numeric literals (e.g., [1, 2, 3, ...]) + if (ivExpression instanceof ListConstructorExpressionNode listExpression) { + return listExpression.expressions().stream() + .allMatch(expr -> expr.kind().equals(SyntaxKind.NUMERIC_LITERAL)); + } + + // Check for toBytes() method called on a string literal or name reference + if (ivExpression instanceof MethodCallExpressionNode methodCallExpression) { + ExpressionNode expression = methodCallExpression.expression(); + if (!expression.kind().equals(SyntaxKind.STRING_LITERAL) && + !expression.kind().equals(SyntaxKind.SIMPLE_NAME_REFERENCE) && + !expression.kind().equals(SyntaxKind.QUALIFIED_NAME_REFERENCE)) { + return false; + } + NameReferenceNode nameReferenceNode = methodCallExpression.methodName(); + if (nameReferenceNode instanceof SimpleNameReferenceNode simpleNameRef) { + return simpleNameRef.name().text().equals(TO_BYTES_METHOD); + } + } + + return false; + } +} diff --git a/compiler-plugin/src/main/java/io/ballerina/stdlib/crypto/compiler/staticcodeanalyzer/functionrules/AvoidWeakCipherAlgorithmsRule.java b/compiler-plugin/src/main/java/io/ballerina/stdlib/crypto/compiler/staticcodeanalyzer/functionrules/AvoidWeakCipherAlgorithmsRule.java new file mode 100644 index 00000000..144f1936 --- /dev/null +++ b/compiler-plugin/src/main/java/io/ballerina/stdlib/crypto/compiler/staticcodeanalyzer/functionrules/AvoidWeakCipherAlgorithmsRule.java @@ -0,0 +1,69 @@ +/* + * Copyright (c) 2025, WSO2 LLC. (http://www.wso2.com). + * + * WSO2 LLC. licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package io.ballerina.stdlib.crypto.compiler.staticcodeanalyzer.functionrules; + +import io.ballerina.stdlib.crypto.compiler.staticcodeanalyzer.FunctionContext; + +import java.util.Optional; + +import static io.ballerina.stdlib.crypto.compiler.staticcodeanalyzer.CryptoAnalyzerUtils.getStringValue; +import static io.ballerina.stdlib.crypto.compiler.staticcodeanalyzer.CryptoRule.AVOID_WEAK_CIPHER_ALGORITHMS; + +/** + * Rule to avoid weak cipher algorithms in crypto functions. + * Analyzes the usage of AES in ECB/CBC modes and RSA with PKCS1 padding. + * + * @since 2.9.1 + */ +public class AvoidWeakCipherAlgorithmsRule implements CryptoFunctionRule { + + public static final String ENCRYPT_AES_ECB = "encryptAesEcb"; + public static final String ENCRYPT_AES_CBC = "encryptAesCbc"; + public static final String ENCRYPT_RSA_ECB = "encryptRsaEcb"; + public static final String PADDING_PARAM = "padding"; + public static final String PKCS1_PADDING = "PKCS1"; + + @Override + public void analyze(FunctionContext context) { + String functionName = context.functionName(); + if (functionName.equals(ENCRYPT_AES_CBC) || functionName.equals(ENCRYPT_AES_ECB) + || (functionName.equals(ENCRYPT_RSA_ECB) && isRsbEcbWithPKCS1Padding(context))) { + context.reporter().reportIssue(context.document(), context.functionLocation(), getRuleId()); + } + } + + @Override + public int getRuleId() { + return AVOID_WEAK_CIPHER_ALGORITHMS.getId(); + } + + @Override + public boolean isApplicable(FunctionContext context) { + String functionName = context.functionName(); + return functionName.equals(ENCRYPT_AES_CBC) || functionName.equals(ENCRYPT_AES_ECB) + || functionName.equals(ENCRYPT_RSA_ECB); + } + + private boolean isRsbEcbWithPKCS1Padding(FunctionContext context) { + Optional paddingExprOpt = getStringValue(PADDING_PARAM, context); + // If padding parameter is not provided, it defaults to PKCS1 padding which is considered weak + return paddingExprOpt + .map(s -> s.equals(PKCS1_PADDING)) + .orElse(true); + } +} diff --git a/compiler-plugin/src/main/java/io/ballerina/stdlib/crypto/compiler/staticcodeanalyzer/functionrules/CryptoFunctionRule.java b/compiler-plugin/src/main/java/io/ballerina/stdlib/crypto/compiler/staticcodeanalyzer/functionrules/CryptoFunctionRule.java new file mode 100644 index 00000000..2cd65d40 --- /dev/null +++ b/compiler-plugin/src/main/java/io/ballerina/stdlib/crypto/compiler/staticcodeanalyzer/functionrules/CryptoFunctionRule.java @@ -0,0 +1,50 @@ +/* + * Copyright (c) 2025, WSO2 LLC. (http://www.wso2.com). + * + * WSO2 LLC. licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package io.ballerina.stdlib.crypto.compiler.staticcodeanalyzer.functionrules; + +import io.ballerina.stdlib.crypto.compiler.staticcodeanalyzer.FunctionContext; + +/** + * Interface for crypto function analysis rules. + * + * @since 2.9.1 + */ +public interface CryptoFunctionRule { + + /** + * Analyze the given crypto function and report issues if any. + * + * @param context Context information required to analyze the function + */ + void analyze(FunctionContext context); + + /** + * Get the unique rule ID for this rule. + * + * @return Unique rule ID + */ + int getRuleId(); + + /** + * Check whether this rule is applicable for the given context. + * + * @param context Context information required to analyze the function + * @return true if the rule is applicable, false otherwise + */ + boolean isApplicable(FunctionContext context); +} diff --git a/compiler-plugin/src/main/resources/rules.json b/compiler-plugin/src/main/resources/rules.json index 6fb391a4..567d9129 100644 --- a/compiler-plugin/src/main/resources/rules.json +++ b/compiler-plugin/src/main/resources/rules.json @@ -13,10 +13,5 @@ "id": 3, "kind": "VULNERABILITY", "description": "Avoid reusing counter mode initialization vectors" - }, - { - "id": 4, - "kind": "VULNERABILITY", - "description": "Secure random number generators should not output predictable values" } ] From 8bfe37445d2d03085d072f70c71d7af0d437f318 Mon Sep 17 00:00:00 2001 From: TharmiganK Date: Fri, 17 Oct 2025 18:45:53 +0530 Subject: [PATCH 02/10] Update spec with scan rules --- docs/spec/spec.md | 739 ++++++++++++++++++++++++++++++++++------------ 1 file changed, 549 insertions(+), 190 deletions(-) diff --git a/docs/spec/spec.md b/docs/spec/spec.md index 28830500..31b8086f 100644 --- a/docs/spec/spec.md +++ b/docs/spec/spec.md @@ -4,7 +4,7 @@ _Owners_: @shafreenAnfar @bhashinee _Reviewers_: @shafreenAnfar _Created_: 2022/08/23 _Updated_: 2025/01/20 -_Edition_: Swan Lake +_Edition_: Swan Lake ## Introduction @@ -17,173 +17,184 @@ If you have any feedback or suggestions about the library, start a discussion vi The conforming implementation of the specification is released and included in the distribution. Any deviation from the specification is considered a bug. ## Contents + 1. [Overview](#1-overview) 2. [Hash](#2-hash) - * 2.1. [MD5](#21-md5) - * 2.2. [SHA1](#22-sha1) - * 2.3. [SHA256](#23-sha256) - * 2.4. [SHA384](#24-sha384) - * 2.5. [SHA512](#25-sha512) - * 2.6. [CRC32B](#26-crc32b) - * 2.7. [KECCAK256](#27-keccak256) + - 2.1. [MD5](#21-md5) + - 2.2. [SHA1](#22-sha1) + - 2.3. [SHA256](#23-sha256) + - 2.4. [SHA384](#24-sha384) + - 2.5. [SHA512](#25-sha512) + - 2.6. [CRC32B](#26-crc32b) + - 2.7. [KECCAK256](#27-keccak256) 3. [HMAC](#3-hmac) - * 3.1. [MD5](#31-md5) - * 3.2. [SHA1](#32-sha1) - * 3.3. [SHA256](#33-sha256) - * 3.4. [SHA384](#34-sha384) - * 3.5. [SHA512](#35-sha512) -4. [Decode private/public key](#4-decode-private-public-keys) - * 4.1. [Decode RSA Private key from PKCS12 file](#41-rsa-decode-private-key-from-pkcs12-file) - * 4.2. [Decode RSA Private key using Private key and Password](#42-decode-rsa-private-key-using-private-key-and-password) - * 4.3. [Decode RSA Private key using Private key content and Password](#43-decode-rsa-private-key-using-private-key-content-and-password) - * 4.4. [Decode RSA Public key from PKCS12 file](#44-decode-rsa-public-key-from-pkcs12-file) - * 4.5. [Decode RSA Public key from the certificate file](#45-decode-rsa-public-key-from-the-certificate-file) - * 4.6. [Decode RSA Public key from the certificate content](#46-decode-rsa-public-key-from-the-certificate-content) - * 4.7. [Decode EC Private key from PKCS12 file](#47-decode-ec-private-key-from-pkcs12-file) - * 4.8. [Decode EC Private key using Private key and Password](#48-decode-ec-private-key-using-private-key-and-password) - * 4.9. [Decode EC Public key from PKCS12 file](#49-decode-ec-public-key-from-pkcs12-file) - * 4.10. [Decode EC Public key from the certificate file](#410-decode-ec-public-key-from-the-certificate-file) - * 4.11. [Build RSA Public key from modulus and exponent parameters](#411-build-rsa-public-key-from-modulus-and-exponent-parameters) - * 4.12. [Decode ML-DSA-65 Private key from PKCS12 file](#412-decode-ml-dsa-65-private-key-from-pkcs12-file) - * 4.13. [Decode ML-DSA-65 Private key using Private key and Password](#413-decode-ml-dsa-65-private-key-using-private-key-and-password) - * 4.14. [Decode ML-DSA-65 Public key from PKCS12 file](#414-decode-ml-dsa-65-public-key-from-pkcs12-file) - * 4.15. [Decode ML-DSA-65 Public key from the certificate file](#415-decode-ml-dsa-65-public-key-from-the-certificate-file) - * 4.16. [Decode ML-KEM-768 Private key from PKCS12 file](#416-decode-ml-kem-768-private-key-from-pkcs12-file) - * 4.17. [Decode ML-KEM-768 Private key using Private key and Password](#417-decode-ml-kem-768-private-key-using-private-key-and-password) - * 4.18. [Decode ML-KEM-768 Public key from PKCS12 file](#418-decode-ml-kem-768-public-key-from-pkcs12-file) - * 4.19. [Decode ML-KEM-768 Public key from the certificate file](#419-decode-ml-kem-768-public-key-from-the-certificate-file) -5. [Encrypt-Decrypt](#5-encrypt-decrypt) - * 5.1. [Encryption](#51-encryption) - * 5.1.1. [RSA](#511-rsa) - * 5.1.2. [AES-CBC](#512-aes-cbc) - * 5.1.3. [AES-ECB](#513-aes-ecb) - * 5.1.4. [AES-GCM](#514-aes-gcm) - * 5.1.5. [PGP](#515-pgp) - * 5.2. [Decryption](#52-decryption) - * 5.2.1. [RSA-ECB](#521-rsa-ecb) - * 5.2.2. [AES-CBC](#522-aes-cbc) - * 5.2.3. [AES-ECB](#523-aes-ecb) - * 5.2.4. [AES-GCM](#524-aes-gcm) - * 5.2.5. [PGP](#525-pgp) + - 3.1. [MD5](#31-md5) + - 3.2. [SHA1](#32-sha1) + - 3.3. [SHA256](#33-sha256) + - 3.4. [SHA384](#34-sha384) + - 3.5. [SHA512](#35-sha512) +4. [Decode private/public key](#4-decode-privatepublic-key) + - 4.1. [Decode RSA Private key from PKCS12 file](#41-decode-rsa-private-key-from-pkcs12-file) + - 4.2. [Decode RSA Private key using Private key and Password](#42-decode-rsa-private-key-using-private-key-and-password) + - 4.3. [Decode RSA Private key using Private key content and Password](#43-decode-rsa-private-key-using-private-key-content-and-password) + - 4.4. [Decode RSA Public key from PKCS12 file](#44-decode-rsa-public-key-from-pkcs12-file) + - 4.5. [Decode RSA Public key from the certificate file](#45-decode-rsa-public-key-from-the-certificate-file) + - 4.6. [Decode RSA Public key from the certificate content](#46-decode-rsa-public-key-from-the-certificate-content) + - 4.7. [Decode EC Private key from PKCS12 file](#47-decode-ec-private-key-from-pkcs12-file) + - 4.8. [Decode EC Private key using Private key and Password](#48-decode-ec-private-key-using-private-key-and-password) + - 4.9. [Decode EC Public key from PKCS12 file](#49-decode-ec-public-key-from-pkcs12-file) + - 4.10. [Decode EC Public key from the certificate file](#410-decode-ec-public-key-from-the-certificate-file) + - 4.11. [Build RSA Public key from modulus and exponent parameters](#411-build-rsa-public-key-from-modulus-and-exponent-parameters) + - 4.12. [Decode ML-DSA-65 Private key from PKCS12 file](#412-decode-ml-dsa-65-private-key-from-pkcs12-file) + - 4.13. [Decode ML-DSA-65 Private key using Private key and Password](#413-decode-ml-dsa-65-private-key-using-private-key-and-password) + - 4.14. [Decode ML-DSA-65 Public key from PKCS12 file](#414-decode-ml-dsa-65-public-key-from-pkcs12-file) + - 4.15. [Decode ML-DSA-65 Public key from the certificate file](#415-decode-ml-dsa-65-public-key-from-the-certificate-file) + - 4.16. [Decode ML-KEM-768 Private key from PKCS12 file](#416-decode-ml-kem-768-private-key-from-pkcs12-file) + - 4.17. [Decode ML-KEM-768 Private key using Private key and Password](#417-decode-ml-kem-768-private-key-using-private-key-and-password) + - 4.18. [Decode ML-KEM-768 Public key from PKCS12 file](#418-decode-ml-kem-768-public-key-from-pkcs12-file) + - 4.19. [Decode ML-KEM-768 Public key from the certificate file](#419-decode-ml-kem-768-public-key-from-the-certificate-file) +5. [Encrypt-Decrypt](#5-encrypt-decrypt) + - 5.1. [Encryption](#51-encryption) + - 5.1.1. [RSA](#511-rsa) + - 5.1.2. [AES-CBC](#512-aes-cbc) + - 5.1.3. [AES-ECB](#513-aes-ecb) + - 5.1.4. [AES-GCM](#514-aes-gcm) + - 5.1.5. [PGP](#515-pgp) + - 5.2. [Decryption](#52-decryption) + - 5.2.1. [RSA-ECB](#521-rsa-ecb) + - 5.2.2. [AES-CBC](#522-aes-cbc) + - 5.2.3. [AES-ECB](#523-aes-ecb) + - 5.2.4. [AES-GCM](#524-aes-gcm) + - 5.2.5. [PGP](#525-pgp) 6. [Sign and Verify](#6-sign-and-verify) - * 6.1. [Sign messages](#61-sign-messages) - * 6.1.1. [RSA-MD5](#611-rsa-md5) - * 6.1.2. [RSA-SHA1](#612-rsa-sha1) - * 6.1.3. [RSA-SHA256](#613-rsa-sha256) - * 6.1.4. [RSA-SHA384](#614-rsa-sha384) - * 6.1.5. [RSA-SHA512](#615-rsa-sha512) - * 6.1.6. [SHA384withECDSA](#616-sha384withecdsa) - * 6.1.7. [SHA256withECDSA](#617-sha256withecdsa) - * 6.1.8. [ML-DSA-65](#618-mldsa65) - * 6.2. [Verify signature](#62-verify-signature) - * 6.2.1. [RSA-MD5](#621-rsa-md5) - * 6.2.2. [RSA-SHA1](#622-rsa-sha1) - * 6.2.3. [RSA-SHA256](#623-rsa-sha256) - * 6.2.4. [RSA-SHA384](#624-rsa-sha384) - * 6.2.5. [RSA-SHA512](#625-rsa-sha512) - * 6.2.6. [SHA384withECDSA](#626-sha384withecdsa) - * 6.2.7. [SHA256withECDSA](#627-sha256withecdsa) - * 6.2.8. [ML-DSA-65](#618-mldsa65) + - 6.1. [Sign messages](#61-sign-messages) + - 6.1.1. [RSA-MD5](#611-rsa-md5) + - 6.1.2. [RSA-SHA1](#612-rsa-sha1) + - 6.1.3. [RSA-SHA256](#613-rsa-sha256) + - 6.1.4. [RSA-SHA384](#614-rsa-sha384) + - 6.1.5. [RSA-SHA512](#615-rsa-sha512) + - 6.1.6. [SHA384withECDSA](#616-sha384withecdsa) + - 6.1.7. [SHA256withECDSA](#617-sha256withecdsa) + - 6.1.8. [ML-DSA-65](#618-ml-dsa-65) + - 6.2. [Verify signature](#62-verify-signature) + - 6.2.1. [RSA-MD5](#621-rsa-md5) + - 6.2.2. [RSA-SHA1](#622-rsa-sha1) + - 6.2.3. [RSA-SHA256](#623-rsa-sha256) + - 6.2.4. [RSA-SHA384](#624-rsa-sha384) + - 6.2.5. [RSA-SHA512](#625-rsa-sha512) + - 6.2.6. [SHA384withECDSA](#626-sha384withecdsa) + - 6.2.7. [SHA256withECDSA](#627-sha256withecdsa) + - 6.2.8. [ML-DSA-65](#628-ml-dsa-65) 7. [Key Derivation Function (KDF)](#7-key-derivation-function-kdf) - * 7.1. [HKDF-SHA256](#71-hkdf-sha256) + - 7.1. [HKDF-SHA256](#71-hkdf-sha256) 8. [Key Exchange Mechanism (KEM)](#8-key-exchange-mechanism-kem) - * 8.1 [Encapsulation](#81-encapsulation) - * 8.1.1 [RSA-KEM](#811-rsa-kem) - * 8.1.2 [ML-KEM-768](#812-ml-kem-768) - * 8.1.3 [RSA-KEM-ML-KEM-768](#813-rsa-kem-ml-kem-768) - * 8.2 [Decapsulation](#81-encapsulation) - * 8.2.1 [RSA-KEM](#821-rsa-kem) - * 8.2.2 [ML-KEM-768](#822-ml-kem-768) - * 8.2.3 [RSA-KEM-ML-KEM-768](#823-rsa-kem-ml-kem-768) + - 8.1 [Encapsulation](#81-encapsulation) + - 8.1.1 [RSA-KEM](#811-rsa-kem) + - 8.1.2 [ML-KEM-768](#812-ml-kem-768) + - 8.1.3 [RSA-KEM-ML-KEM-768](#813-rsa-kem-ml-kem-768) + - 8.2 [Decapsulation](#81-encapsulation) + - 8.2.1 [RSA-KEM](#821-rsa-kem) + - 8.2.2 [ML-KEM-768](#822-ml-kem-768) + - 8.2.3 [RSA-KEM-ML-KEM-768](#823-rsa-kem-ml-kem-768) 9. [Hybrid Public Key Encryption (HPKE)](#9-hybrid-public-key-encryption-hpke) - * 9.1 [Encrypt](#91-encrypt) - * 9.1.1 [ML-KEM-768-HPKE](#911-ml-kem-768-hpke) - * 9.1.2 [RSA-KEM-ML-KEM-768-HPKE](#912-rsa-kem-ml-kem-768-hpke) - * 9.2 [Decrypt](#92-decrypt) - * 9.2.1 [ML-KEM-768-HPKE](#921-ml-kem-768-hpke) - * 9.2.2 [RSA-KEM-ML-KEM-768-HPKE](#922-rsa-kem-ml-kem-768-hpke) + - 9.1 [Encrypt](#91-encrypt) + - 9.1.1 [ML-KEM-768-HPKE](#911-ml-kem-768-hpke) + - 9.1.2 [RSA-KEM-ML-KEM-768-HPKE](#912-rsa-kem-ml-kem-768-hpke) + - 9.2 [Decrypt](#92-decrypt) + - 9.2.1 [ML-KEM-768-HPKE](#921-ml-kem-768-hpke) + - 9.2.2 [RSA-KEM-ML-KEM-768-HPKE](#922-rsa-kem-ml-kem-768-hpke) 10. [Password hashing](#10-password-hashing) - * 10.1 [BCrypt](#101-bcrypt) - * 10.2 [Argon2](#102-argon2) - * 10.3 [PBKDF2](#103-pbkdf2) + - 10.1 [BCrypt](#101-bcrypt) + - 10.2 [Argon2](#102-argon2) + - 10.3 [PBKDF2](#103-pbkdf2) +11. [Static Code Rules](#11-static-code-rules) + - 11.1 [Avoid using insecure cipher modes or padding schemes](#111-avoid-using-insecure-cipher-modes-or-padding-schemes) + - 11.2 [Avoid using fast hashing algorithms](#112-avoid-using-fast-hashing-algorithms) + - 11.3 [Avoid reusing counter mode initialization vectors](#113-avoid-reusing-counter-mode-initialization-vectors) - -## 1. [Overview](#1-overview) +## 1. Overview The Ballerina `crypto` library facilitates APIs to do operations like hashing, HMAC generation, checksum generation, encryption, decryption, signing data digitally, verifying digitally signed data, etc., with different cryptographic algorithms. -## 2. [Hash](#2-hash) +## 2. Hash The `crypto` library supports generating hashes with 5 different hash algorithms MD5, SHA1, SHA256, SHA384, and SHA512. Also, it supports generating the CRC32B checksum. -### 2.1. [MD5](#21-md) +### 2.1. MD5 This API can be used to create the MD5 hash of the given data. + ```ballerina string dataString = "Hello Ballerina"; byte[] data = dataString.toBytes(); byte[] hash = crypto:hashMd5(data); ``` -### 2.2. [SHA1](#22-sha1) +### 2.2. SHA1 This API can be used to create the SHA-1 hash of the given data. + ```ballerina string dataString = "Hello Ballerina"; byte[] data = dataString.toBytes(); byte[] hash = crypto:hashSha1(data); ``` -### 2.3. [SHA256](#23-sha256) +### 2.3. SHA256 This API can be used to create the SHA-256 hash of the given data. + ```ballerina string dataString = "Hello Ballerina"; byte[] data = dataString.toBytes(); byte[] hash = crypto:hashSha256(data); ``` -### 2.4. [SHA384](#24-sha384) +### 2.4. SHA384 This API can be used to create the SHA-384 hash of the given data. + ```ballerina string dataString = "Hello Ballerina"; byte[] data = dataString.toBytes(); byte[] hash = crypto:hashSha384(data); ``` -### 2.5. [SHA512](#25-sha512) +### 2.5. SHA512 This API can be used to create the SHA-512 hash of the given data. + ```ballerina string dataString = "Hello Ballerina"; byte[] data = dataString.toBytes(); byte[] hash = crypto:hashSha512(data); ``` -### 2.6. [CRC32B](#26-crc32b) +### 2.6. CRC32B This API can be used to create the Hex-encoded CRC32B value of the given data. + ```ballerina string stringData = "Hello Ballerina"; byte[] data = stringData.toBytes(); string checksum = crypto:crc32b(data); ``` -### 2.7. [KECCAK256](#27-keccak256) +### 2.7. KECCAK256 This API can be used to create the Hex-encoded KECCAK-256 value of the given data. + ```ballerina string stringData = "Hello Ballerina"; byte[] data = stringData.toBytes(); string checksum = crypto:hashKeccak256(data); ``` -## 3. [HMAC](#3-hmac) +## 3. HMAC The `crypto` library supports generating HMAC with 5 different hash algorithms: MD5, SHA1, SHA256, SHA384, and SHA512. -### 3.1. [MD5](#31-md5) +### 3.1. MD5 This API can be used to create HMAC using the MD5 hash function of the given data. @@ -195,7 +206,7 @@ byte[] key = secret.toBytes(); byte[] hmac = check crypto:hmacMd5(data, key); ``` -### 3.2. [SHA1](#32-sha1) +### 3.2. SHA1 This API can be used to create HMAC using the SHA-1 hash function of the given data. @@ -207,7 +218,7 @@ byte[] key = secret.toBytes(); byte[] hmac = crypto:hmacSha1(data, key); ``` -### 3.3. [SHA256](#33-sha256) +### 3.3. SHA256 This API can be used to create HMAC using the SHA-256 hash function of the given data. @@ -219,7 +230,7 @@ byte[] key = secret.toBytes(); byte[] hmac = check crypto:hmacSha256(data, key); ``` -### 3.4. [SHA384](#34-sha384) +### 3.4. SHA384 This API can be used to create HMAC using the SHA-384 hash function of the given data. @@ -231,7 +242,7 @@ byte[] key = secret.toBytes(); byte[] hmac = check crypto:hmacSha384(data, key); ``` -### 3.5. [SHA512](#35-sha512) +### 3.5. SHA512 This API can be used to create HMAC using the SHA-512 hash function of the given data. @@ -243,11 +254,11 @@ byte[] key = secret.toBytes(); byte[] hmac = check crypto:hmacSha512(data, key); ``` -## 4. [Decode private/public key](#4-decode-private-public-keys) +## 4. Decode private/public key The `crypto` library supports decoding the RSA private key from a `.p12` file and a key file in the `PEM` format. Also, it supports decoding a public key from a `.p12` file and a certificate file in the `X509` format. Additionally, this supports building an RSA public key with the modulus and exponent parameters. -### 4.1. [Decode RSA Private key from PKCS12 file](#41-rsa-decode-private-key-from-pkcs12-file) +### 4.1. Decode RSA Private key from PKCS12 file This API can be used to decode the RSA private key from the given PKCS#12 file. @@ -259,7 +270,7 @@ crypto:KeyStore keyStore = { crypto:PrivateKey privateKey = check crypto:decodeRsaPrivateKeyFromKeyStore(keyStore, "keyAlias", "keyPassword"); ``` -### 4.2. [Decode RSA Private key using Private key and Password](#42-decode-rsa-private-key-using-private-key-and-password) +### 4.2. Decode RSA Private key using Private key and Password This API can be used to decode the RSA private key from the given private key and private key password. @@ -268,7 +279,7 @@ string keyFile = "/path/to/private.key"; crypto:PrivateKey privateKey = check crypto:decodeRsaPrivateKeyFromKeyFile(keyFile, "keyPassword"); ``` -4.3. [Decode RSA Private key using Private key content and Password](#43-decode-rsa-private-key-using-private-key-content-and-password) +## 4.3. Decode RSA Private key using Private key content and Password This API can be used to decode the RSA public key from the given public certificate content as a byte array. @@ -277,7 +288,7 @@ byte[] keyContent = [45,45,45,45,45,66,69,71,73,78,...]; crypto:PrivateKey privateKey = check crypto:decodeRsaPrivateKeyFromContent(keyContent); ``` -### 4.4. [Decode RSA Public key from PKCS12 file](#44-decode-rsa-public-key-from-pkcs12-file) +### 4.4. Decode RSA Public key from PKCS12 file This API can be used to decode the RSA public key from the given PKCS#12 archive file. @@ -289,7 +300,7 @@ crypto:TrustStore trustStore = { crypto:PublicKey publicKey = check crypto:decodeRsaPublicKeyFromTrustStore(trustStore, "keyAlias"); ``` -### 4.5. [Decode RSA Public key from the certificate file](#45-decode-rsa-public-key-from-the-certificate-file) +### 4.5. Decode RSA Public key from the certificate file This API can be used to decode the RSA public key from the given public certificate file. @@ -298,7 +309,7 @@ string certFile = "/path/to/public.cert"; crypto:PublicKey publicKey = check crypto:decodeRsaPublicKeyFromCertFile(certFile); ``` -### 4.6. [Decode RSA Public key from the certificate content](#46-decode-rsa-public-key-from-the-certificate-content) +### 4.6. Decode RSA Public key from the certificate content This API can be used to decode the RSA public key from the given public certificate content as a byte array. @@ -307,7 +318,7 @@ byte[] certFileContent = [45,45,45,45,45,66,69,71,73,78,...]; crypto:PublicKey publicKey = check crypto:decodeRsaPublicKeyFromContent(certFileContent); ``` -### 4.7. [Decode EC Private key from PKCS12 file](#47-decode-ec-private-key-from-pkcs12-file) +### 4.7. Decode EC Private key from PKCS12 file This API can be used to decode the EC private key from the given PKCS#12 file. @@ -319,7 +330,7 @@ crypto:KeyStore keyStore = { crypto:PrivateKey privateKey = check crypto:decodeEcPrivateKeyFromKeyStore(keyStore, "keyAlias", "keyPassword"); ``` -### 4.8. [Decode EC Private key using Private key and Password](#48-decode-ec-private-key-using-private-key-and-password) +### 4.8. Decode EC Private key using Private key and Password This API can be used to decode the EC private key from the given private key and private key password. @@ -328,7 +339,7 @@ string keyFile = "/path/to/private.key"; crypto:PrivateKey privateKey = check crypto:decodeEcPrivateKeyFromKeyFile(keyFile, "keyPassword"); ``` -### 4.9. [Decode EC Public key from PKCS12 file](#49-decode-ec-public-key-from-pkcs12-file) +### 4.9. Decode EC Public key from PKCS12 file This API can be used to decode the RSA public key from the given PKCS#12 archive file. @@ -340,7 +351,7 @@ crypto:TrustStore trustStore = { crypto:PublicKey publicKey = check crypto:decodeEcPublicKeyFromTrustStore(trustStore, "keyAlias"); ``` -### 4.10. [Decode EC Public key from the certificate file](#410-decode-ec-public-key-from-the-certificate-file) +### 4.10. Decode EC Public key from the certificate file This API can be used to decode the EC public key from the given public certificate file. @@ -349,7 +360,7 @@ string certFile = "/path/to/public.cert"; crypto:PublicKey publicKey = check crypto:decodeEcPublicKeyFromCertFile(certFile); ``` -### 4.11. [Build RSA Public key from modulus and exponent parameters](#411-build-rsa-public-key-from-modulus-and-exponent-parameters) +### 4.11. Build RSA Public key from modulus and exponent parameters This API can be used to build the RSA public key from the given modulus and exponent parameters. @@ -362,7 +373,7 @@ string exponent = "AQAB"; crypto:PublicKey publicKey = check crypto:buildRsaPublicKey(modulus, exponent); ``` -### 4.12. [Decode ML-DSA-65 Private key from PKCS12 file](#412-decode-ml-dsa-65-private-key-from-pkcs12-file) +### 4.12. Decode ML-DSA-65 Private key from PKCS12 file This API can be used to decode the ML-DSA-65 private key from the given PKCS#12 file. @@ -374,7 +385,7 @@ crypto:KeyStore keyStore = { crypto:PrivateKey privateKey = check crypto:decodeMlDsa65PrivateKeyFromKeyStore(keyStore, "keyAlias", "keyPassword"); ``` -### 4.13. [Decode ML-DSA-65 Private key using Private key and Password](#413-decode-ml-dsa-65-private-key-using-private-key-and-password) +### 4.13. Decode ML-DSA-65 Private key using Private key and Password This API can be used to decode the ML-DSA-65 private key from the given private key and private key password. @@ -383,7 +394,7 @@ string keyFile = "/path/to/private.key"; crypto:PrivateKey privateKey = check crypto:decodeMlDsa65PrivateKeyFromKeyFile(keyFile, "keyPassword"); ``` -### 4.14. [Decode ML-DSA-65 Public key from PKCS12 file](#414-decode-ml-dsa-65-public-key-from-pkcs12-file) +### 4.14. Decode ML-DSA-65 Public key from PKCS12 file This API can be used to decode the ML-DSA-65 public key from the given PKCS#12 archive file. @@ -395,7 +406,7 @@ crypto:TrustStore trustStore = { crypto:PublicKey publicKey = check crypto:decodeMlDsa65PublicKeyFromTrustStore(trustStore, "keyAlias"); ``` -### 4.15. [Decode ML-DSA-65 Public key from the certificate file](#415-decode-ml-dsa-65-public-key-from-the-certificate-file) +### 4.15. Decode ML-DSA-65 Public key from the certificate file This API can be used to decode the ML-DSA-65 public key from the given public certificate file. @@ -404,7 +415,7 @@ string certFile = "/path/to/public.cert"; crypto:PublicKey publicKey = check crypto:decodeMlDsa65PublicKeyFromCertFile(certFile); ``` -### 4.16. [Decode ML-KEM-768 Private key from PKCS12 file](#416-decode-ml-kem-768-private-key-from-pkcs12-file) +### 4.16. Decode ML-KEM-768 Private key from PKCS12 file This API can be used to decode the ML-KEM-768 private key from the given PKCS#12 file. @@ -416,7 +427,7 @@ crypto:KeyStore keyStore = { crypto:PrivateKey privateKey = check crypto:decodeMlKem768PrivateKeyFromKeyStore(keyStore, "keyAlias", "keyPassword"); ``` -### 4.17. [Decode ML-KEM-768 Private key using Private key and Password](#417-decode-ml-kem-768-private-key-using-private-key-and-password) +### 4.17. Decode ML-KEM-768 Private key using Private key and Password This API can be used to decode the ML-KEM-768 private key from the given private key and private key password. @@ -425,7 +436,7 @@ string keyFile = "/path/to/private.key"; crypto:PrivateKey privateKey = check crypto:decodeMlKem768PrivateKeyFromKeyFile(keyFile, "keyPassword"); ``` -### 4.18. [Decode ML-KEM-768 Public key from PKCS12 file](#418-decode-ml-kem-768-public-key-from-pkcs12-file) +### 4.18. Decode ML-KEM-768 Public key from PKCS12 file This API can be used to decode the ML-KEM-768 public key from the given PKCS#12 archive file. @@ -437,7 +448,7 @@ crypto:TrustStore trustStore = { crypto:PublicKey publicKey = check crypto:decodeMlKem768PublicKeyFromTrustStore(trustStore, "keyAlias"); ``` -### 4.19. [Decode ML-KEM-768 Public key from the certificate file](#419-decode-ml-kem-768-public-key-from-the-certificate-file) +### 4.19. Decode ML-KEM-768 Public key from the certificate file This API can be used to decode the ML-KEM-768 public key from the given public certificate file. @@ -446,13 +457,13 @@ string certFile = "/path/to/public.cert"; crypto:PublicKey publicKey = check crypto:decodeMlKem768PublicKeyFromCertFile(certFile); ``` -## 5. [Encrypt-Decrypt](#5-encrypt-decrypt) +## 5. Encrypt-Decrypt The `crypto` library supports both symmetric key encryption/decryption and asymmetric key encryption/decryption. The RSA algorithm can be used for asymmetric-key encryption/decryption with the use of private and public keys. The AES algorithm can be used for symmetric-key encryption/decryption with the use of a shared key. -### 5.1. [Encryption](#51-encryption) +### 5.1. Encryption -#### 5.1.1. [RSA](#511-rsa) +#### 5.1.1. RSA This API can be used to create the RSA-encrypted value of the given data. @@ -467,7 +478,7 @@ crypto:PublicKey publicKey = check crypto:decodeRsaPublicKeyFromTrustStore(keySt byte[] cipherText = check crypto:encryptRsaEcb(data, publicKey); ``` -#### 5.1.2. [AES-CBC](#512-aes-cbc) +#### 5.1.2. AES-CBC This API can be used to create the AES-CBC-encrypted value for the given data. @@ -485,7 +496,7 @@ foreach int i in 0...15 { byte[] cipherText = check crypto:encryptAesCbc(data, key, initialVector); ``` -#### 5.1.3. [AES-ECB](#513-aes-ecb) +#### 5.1.3. AES-ECB This API can be used to create the AES-ECB-encrypted value for the given data. @@ -499,7 +510,7 @@ foreach int i in 0...15 { byte[] cipherText = check crypto:encryptAesEcb(data, key); ``` -#### 5.1.4. [AES-GCM](#514-aes-gcm) +#### 5.1.4. AES-GCM This API can be used to create the AES-GCM-encrypted value for the given data. @@ -517,7 +528,7 @@ foreach int i in 0...15 { byte[] cipherText = check crypto:encryptAesGcm(data, key, initialVector); ``` -#### 5.1.5. [PGP](#515-pgp) +#### 5.1.5. PGP This API can be used to create the PGP-encrypted value for the given data. @@ -554,9 +565,9 @@ stream inputStream = check io:fileReadBlocksAsStream("input.txt" stream|crypto:Error encryptedStream = crypto:encryptStreamAsPgp(inputStream, "public_key.asc"); ``` -### 5.2. [Decryption](#52-decryption) +### 5.2. Decryption -#### 5.2.1. [RSA-ECB](#521-rsa-ecb) +#### 5.2.1. RSA-ECB This API can be used to create the RSA-decrypted value for the given RSA-encrypted data. @@ -573,7 +584,7 @@ byte[] cipherText = check crypto:encryptRsaEcb(data, publicKey); byte[] plainText = check crypto:decryptRsaEcb(cipherText, privateKey); ``` -#### 5.2.2. [AES-CBC](#522-aes-cbc) +#### 5.2.2. AES-CBC This API can be used to create the AES-CBC-decrypted value for the given AES-CBC-encrypted data. @@ -592,7 +603,7 @@ byte[] cipherText = check crypto:encryptAesCbc(data, key, initialVector); byte[] plainText = check crypto:decryptAesCbc(cipherText, key, initialVector); ``` -#### 5.2.3. [AES-ECB](#523-aes-ecb) +#### 5.2.3. AES-ECB This API can be used to create the AES-ECB-decrypted value for the given AES-ECB-encrypted data. @@ -607,7 +618,7 @@ byte[] cipherText = check crypto:encryptAesEcb(data, key); byte[] plainText = check crypto:decryptAesEcb(cipherText, key); ``` -#### 5.2.4. [AES-GCM](#524-aes-gcm) +#### 5.2.4. AES-GCM This API can be used to create the AES-GCM-decrypted value for the given AES-GCM-encrypted data. @@ -626,7 +637,7 @@ byte[] cipherText = check crypto:encryptAesGcm(data, key, initialVector); byte[] plainText = check crypto:decryptAesGcm(cipherText, key, initialVector); ``` -#### 5.2.5. [PGP](#525-pgp) +#### 5.2.5. PGP This API can be used to create the PGP-decrypted value for the given PGP-encrypted data. @@ -649,13 +660,13 @@ stream inputStream = check io:fileReadBlocksAsStream("pgb_encryp stream|crypto:Error decryptedStream = crypto:decryptStreamFromPgp(inputStream, "private_key.asc", passphrase); ``` -## 6. [Sign and Verify](#6-sign-and-verify) +## 6. Sign and Verify The `crypto` library supports signing data using the RSA private key and verification of the signature using the RSA public key. This supports MD5, SHA1, SHA256, SHA384, and SHA512 digesting algorithms, and ML-DSA-65 post-quantum signature algorithm as well. -### 6.1. [Sign messages](#51-sign-messages) +### 6.1. Sign messages -#### 6.1.1. [RSA-MD5](#611-rsa-md5) +#### 6.1.1. RSA-MD5 This API can be used to create the RSA-MD5 based signature value for the given data. @@ -670,7 +681,7 @@ crypto:PrivateKey privateKey = check crypto:decodeRsaPrivateKeyFromKeyStore(keyS byte[] signature = check crypto:signRsaMd5(data, privateKey); ``` -#### 6.1.2. [RSA-SHA1](#612-rsa-sha1) +#### 6.1.2. RSA-SHA1 This API can be used to create the RSA-SHA1 based signature value for the given data. @@ -685,7 +696,7 @@ crypto:PrivateKey privateKey = check crypto:decodeRsaPrivateKeyFromKeyStore(keyS byte[] signature = check crypto:signRsaSha1(data, privateKey); ``` -#### 6.1.3. [RSA-SHA256](#613-rsa-sha256) +#### 6.1.3. RSA-SHA256 This API can be used to create the RSA-SHA256 based signature value for the given data. @@ -700,7 +711,7 @@ crypto:PrivateKey privateKey = check crypto:decodeRsaPrivateKeyFromKeyStore(keyS byte[] signature = check crypto:signRsaSha256(data, privateKey); ``` -#### 6.1.4. [RSA-SHA384](#614-rsa-sha384) +#### 6.1.4. RSA-SHA384 This API can be used to create the RSA-SHA384 based signature value for the given data. @@ -715,7 +726,7 @@ crypto:PrivateKey privateKey = check crypto:decodeRsaPrivateKeyFromKeyStore(keyS byte[] signature = check crypto:signRsaSha384(data, privateKey); ``` -#### 6.1.5. [RSA-SHA512](#615-rsa-sha512) +#### 6.1.5. RSA-SHA512 This API can be used to create the RSA-SHA512 based signature value for the given data. @@ -730,7 +741,7 @@ crypto:PrivateKey privateKey = check crypto:decodeRsaPrivateKeyFromKeyStore(keyS byte[] signature = check crypto:signRsaSha512(data, privateKey); ``` -#### 6.1.6. [SHA384withECDSA](#616-sha384withecdsa) +#### 6.1.6. SHA384withECDSA This API can be used to create the SHA384withECDSA based signature value for the given data. @@ -745,7 +756,7 @@ crypto:PrivateKey privateKey = check crypto:decodeEcPrivateKeyFromKeyStore(keySt byte[] signature = check crypto:signSha384withEcdsa(data, privateKey); ``` -#### 6.1.7. [SHA256withECDSA](#617-sha256withecdsa) +#### 6.1.7. SHA256withECDSA This API can be used to create the SHA256withECDSA based signature value for the given data. @@ -760,7 +771,7 @@ crypto:PrivateKey privateKey = check crypto:decodeEcPrivateKeyFromKeyStore(keySt byte[] signature = check crypto:signSha256withEcdsa(data, privateKey); ``` -#### 6.1.8. [ML-DSA-65](#618-mldsa65) +#### 6.1.8. ML-DSA-65 This API can be used to create the ML-DSA-65 based signature value for the given data. @@ -775,9 +786,9 @@ crypto:PrivateKey privateKey = check crypto:decodeMlDsa65PrivateKeyFromKeyStore( byte[] signature = check crypto:signMlDsa65(data, privateKey); ``` -### 6.2. [Verify signature](#62-verify-signature) +### 6.2. Verify signature -#### 6.2.1. [RSA-MD5](#621-rsa-md5) +#### 6.2.1. RSA-MD5 This API can be used to verify the RSA-MD5 based signature. @@ -794,7 +805,7 @@ crypto:PublicKey publicKey = check crypto:decodeRsaPublicKeyFromTrustStore(keySt boolean validity = check crypto:verifyRsaMd5Signature(data, signature, publicKey); ``` -#### 6.2.2. [RSA-SHA1](#622-rsa-sha1) +#### 6.2.2. RSA-SHA1 This API can be used to verify the RSA-SHA1 based signature. @@ -811,7 +822,7 @@ crypto:PublicKey publicKey = check crypto:decodeRsaPublicKeyFromTrustStore(keySt boolean validity = check crypto:verifyRsaSha1Signature(data, signature, publicKey); ``` -#### 6.2.3. [RSA-SHA256](#623-rsa-sha256) +#### 6.2.3. RSA-SHA256 This API can be used to verify the RSA-SHA256 based signature. @@ -828,7 +839,7 @@ crypto:PublicKey publicKey = check crypto:decodeRsaPublicKeyFromTrustStore(keySt boolean validity = check crypto:verifyRsaSha256Signature(data, signature, publicKey); ``` -#### 6.2.4. [RSA-SHA384](#624-rsa-sha384) +#### 6.2.4. RSA-SHA384 This API can be used to verify the RSA-SHA384 based signature. @@ -845,7 +856,7 @@ crypto:PublicKey publicKey = check crypto:decodeRsaPublicKeyFromTrustStore(keySt boolean validity = check crypto:verifyRsaSha384Signature(data, signature, publicKey); ``` -#### 6.2.5. [RSA-SHA512](#625-rsa-sha512) +#### 6.2.5. RSA-SHA512 This API can be used to verify the RSA-SHA512 based signature. @@ -862,7 +873,7 @@ crypto:PublicKey publicKey = check crypto:decodeRsaPublicKeyFromTrustStore(keySt boolean validity = check crypto:verifyRsaSha512Signature(data, signature, publicKey); ``` -#### 6.2.6. [SHA384withECDSA](#626-sha384withecdsa) +#### 6.2.6. SHA384withECDSA This API can be used to verify the SHA384withECDSA based signature. @@ -879,7 +890,7 @@ crypto:PublicKey publicKey = check crypto:decodeEcPublicKeyFromTrustStore(keySto boolean validity = check crypto:verifySha384withEcdsaSignature(data, signature, publicKey); ``` -#### 6.2.7. [SHA256withECDSA](#627-sha256withecdsa) +#### 6.2.7. SHA256withECDSA This API can be used to verify the SHA256withECDSA based signature. @@ -896,7 +907,7 @@ crypto:PublicKey publicKey = check crypto:decodeEcPublicKeyFromTrustStore(keySto boolean validity = check crypto:verifySha256withEcdsaSignature(data, signature, publicKey); ``` -#### 6.2.8. [ML-DSA-65](#628-mldsa65) +#### 6.2.8. ML-DSA-65 This API can be used to verify the ML-DSA-65 based signature. @@ -913,12 +924,11 @@ crypto:PublicKey publicKey = check crypto:decodeMlDsa65PublicKeyFromTrustStore(k boolean validity = check crypto:verifyMlDsa65Signature(data, signature, publicKey); ``` - -## 7. [Key Derivation Function (KDF)](#7-key-derivation-function-kdf) +## 7. Key Derivation Function (KDF) The `crypto` module supports HMAC-based Key Derivation Function (HKDF). HKDF is a key derivation function that uses a Hash-based Message Authentication Code (HMAC) to derive keys. -### 7.1. [HKDF-SHA256](#71-hkdf-sha256) +### 7.1. HKDF-SHA256 This API can be used to create HKDF using the SHA256 hash function of the given data. @@ -928,15 +938,15 @@ byte[] key = secret.toBytes(); byte[] hash = crypto:hkdfSha256(key, 32); ``` -## 8. [Key Exchange Mechanism (KEM)](#8-key-exchange-mechanism-kem) +## 8. Key Exchange Mechanism (KEM) The `crypto` module supports Key Exchange Mechanisms (KEM). It includes RSA-KEM and post-quantum ML-KEM-768 for both encapsulation and decapsulation. -### 8.1. [Encapsulation](#81-encapsulation) - -#### 8.1.1. [RSA-KEM](#811-rsa-kem) +### 8.1. Encapsulation -This API can be used to create shared secret and its encapsulation using RSA-KEM function. +#### 8.1.1. RSA-KEM + +This API can be used to create shared secret and its encapsulation using RSA-KEM function. ```ballerina crypto:KeyStore keyStore = { @@ -946,10 +956,10 @@ crypto:KeyStore keyStore = { crypto:PublicKey publicKey = check crypto:decodeRsaPublicKeyFromTrustStore(keyStore, "keyAlias"); crypto:EncapsulationResult encapsulationResult = check crypto:encapsulateRsaKem(publicKey); ``` - -#### 8.1.2. [ML-KEM-768](#812-ml-kem-768) -This API can be used to create shared secret and its encapsulation using ML-KEM-768 function. +#### 8.1.2. ML-KEM-768 + +This API can be used to create shared secret and its encapsulation using ML-KEM-768 function. ```ballerina crypto:KeyStore keyStore = { @@ -959,10 +969,10 @@ crypto:KeyStore keyStore = { crypto:PublicKey publicKey = check crypto:decodeMlKem768PublicKeyFromTrustStore(keyStore, "keyAlias"); crypto:EncapsulationResult encapsulationResult = check crypto:encapsulateMlKem768(publicKey); ``` - -#### 8.1.3. [RSA-KEM-ML-KEM-768](#813-rsa-kem-ml-kem-768) -This API can be used to create shared secret and its encapsulation using RSA-KEM-ML-KEM-768 function. +#### 8.1.3. RSA-KEM-ML-KEM-768 + +This API can be used to create shared secret and its encapsulation using RSA-KEM-ML-KEM-768 function. ```ballerina crypto:KeyStore mlkemKeyStore = { @@ -982,11 +992,11 @@ crypto:PrivateKey rsaPrivateKey = check crypto:decodeRsaPrivateKeyFromKeyStore(r byte[] sharedSecret = check crypto:decapsulateRsaKemMlKem768(encapsulatedSecret, rsaPrivateKey, mlkemPrivateKey); ``` -### 8.2. [Decapsulation](#81-encapsulation) - -#### 8.2.1. [RSA-KEM](#821-rsa-kem) +### 8.2. Decapsulation + +#### 8.2.1. RSA-KEM -This API can be used to decapsulate shared secret using RSA-KEM function of the given data. +This API can be used to decapsulate shared secret using RSA-KEM function of the given data. ```ballerina crypto:KeyStore keyStore = { @@ -999,10 +1009,10 @@ byte[] encapsulatedSecret = encapsulationResult.encapsulatedSecret; crypto:PrivateKey privateKey = check crypto:decodeRsaPrivateKeyFromKeyStore(keyStore, "keyAlias", "keyStorePassword"); byte[] sharedSecret = check crypto:decapsulateRsaKem(encapsulatedSecret, privateKey); ``` - -#### 8.2.2. [ML-KEM-768](#822-ml-kem-768) -This API can be used to decapsulate shared secret using ML-KEM-768 function of the given data. +#### 8.2.2. ML-KEM-768 + +This API can be used to decapsulate shared secret using ML-KEM-768 function of the given data. ```ballerina crypto:KeyStore keyStore = { @@ -1015,10 +1025,10 @@ byte[] encapsulatedSecret = encapsulationResult.encapsulatedSecret; crypto:PrivateKey privateKey = check crypto:decodeMlKem768PrivateKeyFromKeyStore(keyStore, "keyAlias", "keyStorePassword"); byte[] sharedSecret = check crypto:decapsulateMlKem768(encapsulatedSecret, privateKey); ``` - -#### 8.2.3. [RSA-KEM-ML-KEM-768](#823-rsa-kem-ml-kem-768) -This API can be used to decapsulate shared secret using RSA-KEM-ML-KEM-768 function of the given data. +#### 8.2.3. RSA-KEM-ML-KEM-768 + +This API can be used to decapsulate shared secret using RSA-KEM-ML-KEM-768 function of the given data. ```ballerina crypto:KeyStore mlkemKeyStore = { @@ -1038,13 +1048,13 @@ crypto:PrivateKey rsaPrivateKey = check crypto:decodeRsaPrivateKeyFromKeyStore(r byte[] sharedSecret = check crypto:decapsulateRsaKemMlKem768(encapsulatedSecret, rsaPrivateKey, mlkemPrivateKey); ``` -## 9. [Hybrid Public Key Encryption (HPKE)](#9-hybrid-public-key-encryption-hpke) +## 9. Hybrid Public Key Encryption (HPKE) The `crypto` module supports Hybrid Public Key Encryption (HPKE). It supports post-quantum ML-KEM-768-HPKE and RSA-KEM-ML-KEM-768-HPKE for encryption and decryption. -### 9.1. [Encrypt](#91-encrypt) - -#### 9.1.1. [ML-KEM-768-HPKE](#911-ml-kem-768-hpke) +### 9.1. Encrypt + +#### 9.1.1. ML-KEM-768-HPKE This API can be used to create the ML-KEM-768-hybrid-encrypted value of the given data. @@ -1058,8 +1068,8 @@ crypto:KeyStore keyStore = { crypto:PublicKey publicKey = check crypto:decodeMlKem768PublicKeyFromTrustStore(keyStore, "keyAlias"); crypto:HybridEncryptionResult encryptionResult = check crypto:encryptMlKem768Hpke(data, publicKey); ``` - -#### 9.1.2. [RSA-KEM-ML-KEM-768-HPKE](#912-rsa-kem-ml-kem-768-hpke) + +#### 9.1.2. RSA-KEM-ML-KEM-768-HPKE This API can be used to create the RSA-KEM-ML-KEM-768-hybrid-encrypted value of the given data. @@ -1079,9 +1089,9 @@ crypto:PublicKey rsaPublicKey = check crypto:decodeRsaPublicKeyFromTrustStore(rs crypto:HybridEncryptionResult encryptionResult = check crypto:encryptRsaKemMlKem768Hpke(data, rsaPublicKey, mlkemPublicKey); ``` -### 9.2. [Decrypt](#92-decrypt) - -#### 9.2.1. [ML-KEM-768-HPKE](#921-ml-kem-768-hpke) +### 9.2. Decrypt + +#### 9.2.1. ML-KEM-768-HPKE This API can be used to create the ML-KEM-768-hybrid-decrypted value of the given data. @@ -1099,8 +1109,8 @@ byte[] encapsulatedKey = encryptionResult.encapsulatedSecret; crypto:PrivateKey privateKey = check crypto:decodeMlKem768PrivateKeyFromKeyStore(keyStore, "keyAlias", "keyStorePassword"); byte[] decryptedData = check crypto:decryptMlKem768Hpke(cipherText, encapsulatedKey, privateKey); ``` - -#### 9.2.2. [RSA-KEM-ML-KEM-768-HPKE](#922-rsa-kem-ml-kem-768-hpke) + +#### 9.2.2. RSA-KEM-ML-KEM-768-HPKE This API can be used to create the RSA-KEM-ML-KEM-768-hybrid-decrypted value of the given data. @@ -1125,11 +1135,11 @@ crypto:PrivateKey rsaPrivateKey = check crypto:decodeRsaPrivateKeyFromKeyStore(r byte[] decryptedData = check crypto:decryptRsaKemMlKem768Hpke(cipherText, encapsulatedKey, rsaPrivateKey, mlkemPrivateKey); ``` -## 10. [Password Hashing](#10-password-hashing) +## 10. Password Hashing The `crypto` module provides password hashing using BCrypt and Argon2id algorithms for secure password storage. -### 10.1 [BCrypt](#101-bcrypt) +### 10.1 BCrypt Implements the BCrypt password hashing algorithm based on the Blowfish cipher. @@ -1138,6 +1148,7 @@ public isolated function hashBcrypt(string password, int workFactor = 12) return ``` Parameters: + - `password`: The plain text password to hash - `workFactor`: Computational complexity factor (4-31, default: 12) @@ -1146,6 +1157,7 @@ public isolated function verifyBcrypt(string password, string hashedPassword) re ``` Example: + ```ballerina string password = "your-password"; // Hash with default work factor (12) @@ -1155,7 +1167,7 @@ string hashedPassword2 = check crypto:hashBcrypt(password, 14); boolean isValid = check crypto:verifyBcrypt(password, hashedPassword1); ``` -### 10.2 [Argon2](#102-argon2) +### 10.2 Argon2 Implements the Argon2id variant of the Argon2 password hashing algorithm, optimized for both high memory usage and GPU resistance. @@ -1165,6 +1177,7 @@ public isolated function hashArgon2(string password, int iterations = 3, ``` Parameters: + - `password`: The plain text password to hash - `iterations`: Number of iterations (default: 3) - `memory`: Memory usage in KB (minimum: 8192, default: 65536) @@ -1177,6 +1190,7 @@ public isolated function verifyArgon2(string password, string hashedPassword) re ``` Example: + ```ballerina string password = "your-password"; // Hash with default parameters @@ -1186,7 +1200,7 @@ string hashedPassword2 = check crypto:hashArgon2(password, iterations = 4, memor boolean isValid = check crypto:verifyArgon2(password, hashedPassword1); ``` -### 10.3 [PBKDF2](#103-pbkdf2) +### 10.3 PBKDF2 Implements the PBKDF2 (Password-Based Key Derivation Function 2) algorithm for password hashing. @@ -1232,4 +1246,349 @@ string password = "mySecurePassword123"; string hashedPassword = "$pbkdf2-sha256$i=10000$salt$hash"; // Verify the hashed password boolean isValid = check crypto:verifyPbkdf2(password, hashedPassword); -``` \ No newline at end of file +``` + +## 11. Static Code Rules + +The following static code rules are applied to the Crypto module. + +| Id | Kind | Description | +|--------------------|---------------|----------------------------------------------------------------------| +| ballerina/crypto:1 | VULNERABILITY | Avoid using insecure cipher modes or padding schemes | +| ballerina/crypto:2 | VULNERABILITY | Avoid using fast hashing algorithms | +| ballerina/crypto:3 | VULNERABILITY | Avoid reusing counter mode initialization vectors | + +### 11.1 Avoid using insecure cipher modes or padding schemes + +Using weak or outdated encryption modes and padding schemes can compromise the security of encrypted data, even when strong algorithms are used. + +## 11.1.1. Why this is an issue? + +Encryption algorithms are essential for protecting sensitive information and ensuring secure communications. When implementing encryption, it's critical to select not only strong algorithms but also secure modes of operation and padding schemes. Using weak or outdated encryption modes can compromise the security of otherwise strong algorithms. + +The security risks of using weak encryption modes include: + +- Data confidentiality breaches where encrypted content becomes readable +- Modification of encrypted data without detection +- Pattern recognition in encrypted data that reveals information about the plaintext +- Replay attacks where valid encrypted data is reused maliciously +- Known-plaintext attacks that can reveal encryption keys + +## 11.1.2. What is the potential impact? + +Common vulnerable patterns include: + +- Using ECB (Electronic Codebook) mode which doesn't hide data patterns +- Implementing CBC (Cipher Block Chaining) without integrity checks +- Using RSA encryption without proper padding schemes +- Relying on outdated padding methods like PKCS1v1.5 +- Using stream ciphers with insufficient initialization vectors + +## 11.1.3. How can I fix this? + +Choose secure encryption modes and padding schemes that provide both confidentiality and integrity protection. + +### 11.1.3.1 AES Encryption Example + +**Non-compliant code :** + +```ballerina +byte[] cipherText = check crypto:encryptAesEcb(data, key); +``` + +For AES, the weakest mode is ECB (Electronic Codebook). Repeated blocks of data are encrypted to the same value, making them easy to identify and reducing the difficulty of recovering the original cleartext. + +```ballerina +byte[] cipherText = check crypto:encryptAesCbc(data, key, initialVector); +``` + +Unauthenticated modes such as CBC (Cipher Block Chaining) may be used but are prone to attacks that manipulate the ciphertext (like padding oracle attacks). They must be used with caution and additional integrity checks. + +**Compliant code:** + +```ballerina +byte[] cipherText = check crypto:encryptAesGcm(data, key, initialVector); +``` + +AES-GCM (Galois/Counter Mode) provides authenticated encryption, ensuring both confidentiality and integrity of the encrypted data. + +### 11.1.3.2 RSA Encryption Example + +**Non-compliant code :** + +```ballerina +// Default padding is PKCS1 +byte[] cipherText = check crypto:encryptRsaEcb(data, publicKey); + +cipherText = check crypto:encryptRsaEcb(data, publicKey, crypto:PKCS1); +``` + +For RSA, avoid using PKCS1v1.5 padding as it is vulnerable to various attacks. Instead, use OAEP (Optimal Asymmetric Encryption Padding) which provides better security. + +**Compliant code:** + +```ballerina +byte[] cipherText = check crypto:encryptRsaEcb(data, publicKey, crypto:OAEPwithMD5andMGF1); +``` + +OAEP such as OAEPwithMD5andMGF1, OAEPWithSHA1AndMGF1, OAEPWithSHA256AndMGF1, OAEPwithSHA384andMGF1, and OAEPwithSHA512andMGF1 should be used for RSA encryption to enhance security. + +## Additional Resources + +- OWASP - [Top 10 2021 Category A2 - Cryptographic Failures](https://owasp.org/Top10/A02_2021-Cryptographic_Failures/) +- OWASP - [Top 10 2017 Category A3 - Sensitive Data Exposure](https://owasp.org/www-project-top-ten/2017/A3_2017-Sensitive_Data_Exposure) +- OWASP - [Top 10 2017 Category A6 - Security Misconfiguration](https://owasp.org/www-project-top-ten/2017/A6_2017-Security_Misconfiguration) +- OWASP - [Mobile AppSec Verification Standard - Cryptography Requirements](https://mas.owasp.org/checklists/MASVS-CRYPTO/) +- OWASP - [Mobile Top 10 2016 Category M5 - Insufficient Cryptography](https://owasp.org/www-project-mobile-top-10/2016-risks/m5-insufficient-cryptography) +- OWASP - [Mobile Top 10 2024 Category M10 - Insufficient Cryptography](https://owasp.org/www-project-mobile-top-10/2023-risks/m10-insufficient-cryptography) +- CWE - [CWE-327 - Use of a Broken or Risky Cryptographic Algorithm](https://cwe.mitre.org/data/definitions/327) +- CWE - [CWE-780 - Use of RSA Algorithm without OAEP](https://cwe.mitre.org/data/definitions/780) +- [CERT, MSC61-J.](https://wiki.sei.cmu.edu/confluence/x/hDdGBQ) - Do not use insecure or weak cryptographic algorithms + +### 11.2 Avoid using fast hashing algorithms + +Storing passwords in plaintext or using fast hashing algorithms creates significant security vulnerabilities. If an attacker gains access to your database, plaintext passwords are immediately compromised. Similarly, passwords hashed with fast algorithms (like MD5, SHA-1, or SHA-256 without sufficient iterations) can be rapidly cracked using modern hardware. + +## 11.2.1. Why this is an issue? + +When using PBKDF2 (Password-Based Key Derivation Function 2), the iteration count is critical for security. Higher iteration counts increase the computational effort required to hash passwords, making brute-force attacks more difficult and time-consuming. + +Following are the OWASP recommended parameters: + +For BCrypt: + +- Use a work factor of 10 or more +- Only use BCrypt for password storage in legacy systems where Argon2 and scrypt are not available +- Be aware of BCrypt's 72-byte password length limit + +For Argon2: + +- Use the Argon2id variant (which Ballerina implements) +- Minimum configuration of 19 MiB (19,456 KB) of memory +- An iteration count of at least 2 +- At least 1 degree of parallelism + +For PBKDF2: + +- PBKDF2-HMAC-SHA1: 1,300,000 iterations +- PBKDF2-HMAC-SHA256: 600,000 iterations (recommended by NIST) +- PBKDF2-HMAC-SHA512: 210,000 iterations + +If performance constraints make these recommendations impractical, the iteration count should never be lower than 100,000. + +## 11.2.2. What is the potential impact? + +The security risks of using fast hashing algorithms include: + +- Password databases become vulnerable to brute-force attacks +- Dictionary attacks can quickly test common passwords +- Rainbow table attacks can reverse hash values +- GPU-accelerated cracking tools can process billions of hashes per second +- Credential stuffing attacks using compromised password lists + +## 11.2.3. How can I fix this? + +Use secure password hashing algorithms with appropriate parameters that provide sufficient computational cost to resist brute-force attacks. + +### 11.2.3.1 BCrypt Hashing Example + +**Non-compliant code:** + +```ballerina +public function main() returns error? { + string password = "mySecurePassword123"; + // Using insufficient work factor + string hashedPassword = check crypto:hashBcrypt(password, 4); + io:println("Hashed Password: ", hashedPassword); +} +``` + +Using BCrypt with a work factor below 10 is insufficient and vulnerable to brute-force attacks. + +**Compliant code:** + +```ballerina +public function hashPassword() returns error? { + string password = "mySecurePassword123"; + // Using sufficient work factor (14 or higher for better security) + string hashedPassword = check crypto:hashBcrypt(password, 14); + io:println("Hashed Password: ", hashedPassword); +} +``` + +### 11.2.3.2 Argon2 Hashing Example + +**Non-compliant code:** + +```ballerina +public function main() returns error? { + string password = "mySecurePassword123"; + // Using insufficient memory configuration + string hashedPassword = check crypto:hashArgon2(password, memory = 4096); + io:println("Hashed Password: ", hashedPassword); +} +``` + +Using Argon2 with insufficient memory (less than 19,456 KB) makes it vulnerable to attacks. + +**Compliant code:** + +```ballerina +public function hashPassword() returns error? { + string password = "mySecurePassword123"; + // Using recommended parameters: sufficient memory, iterations, and parallelism + string hashedPassword = check crypto:hashArgon2(password, iterations = 3, memory = 65536, parallelism = 4); + io:println("Hashed Password: ", hashedPassword); +} +``` + +### 11.2.3.3 PBKDF2 Hashing Example + +**Non-compliant code:** + +```ballerina +public function main() returns error? { + string password = "mySecurePassword123"; + // Using default settings with insufficient iterations + string hashedPassword = check crypto:hashPbkdf2(password); + io:println("Hashed Password: ", hashedPassword); +} +``` + +Using PBKDF2 with insufficient iterations (default 10,000) is vulnerable to brute-force attacks. + +**Compliant code:** + +```ballerina +public function hashPassword() returns error? { + string password = "mySecurePassword123"; + // Using sufficient iterations as recommended by NIST + string hashedPassword = check crypto:hashPbkdf2(password, iterations = 600000, algorithm = SHA256); + io:println("Hashed Password: ", hashedPassword); +} +``` + +## 11.2.4 Additional Resources + +- OWASP - [Top 10 2021 Category A2 - Cryptographic Failures](https://owasp.org/Top10/A02_2021-Cryptographic_Failures/) +- OWASP - [Top 10 2021 Category A4 - Insecure Design](https://owasp.org/Top10/A04_2021-Insecure_Design/) +- OWASP - [Top 10 2017 Category A3 - Sensitive Data Exposure](https://owasp.org/www-project-top-ten/2017/A3_2017-Sensitive_Data_Exposure) +- OWASP - [Mobile Top 10 2024 Category M10 - Insufficient Cryptography](https://owasp.org/www-project-mobile-top-10/2023-risks/m10-insufficient-cryptography) +- CWE - [CWE-256 - Plaintext Storage of a Password](https://cwe.mitre.org/data/definitions/256) +- CWE - [CWE-916 - Use of Password Hash With Insufficient Computational Effort](https://cwe.mitre.org/data/definitions/916) +- STIG Viewer - [Application Security and Development: V-222542](https://stigviewer.com/stigs/application_security_and_development/2024-12-06/finding/V-222542) - The application must only store cryptographic representations of passwords. + +### 11.3 Avoid reusing counter mode initialization vectors + +When using encryption algorithms in counter mode (such as AES-GCM, AES-CCM, or AES-CTR), initialization vectors (IVs) or nonces should never be reused with the same encryption key. Reusing IVs with the same key can completely compromise the security of the encryption. + +## 11.3.1. Why this is an issue? + +Counter mode encryption relies on unique initialization vectors to ensure security. When the same IV is used with the same encryption key for different plaintexts, it creates serious vulnerabilities that can lead to: + +- Exposure of encrypted data +- Ability for attackers to forge authenticated messages +- Recovery of the authentication key in some cases +- Disclosure of plaintext by XORing two ciphertexts created with the same IV and key + +In modes like GCM (Galois Counter Mode), the initialization vector must be unique for each encryption operation. When an IV is reused, an attacker who observes multiple encrypted messages can perform cryptanalysis to recover the plaintext or even the encryption key. + +The security risks of reusing IVs in counter mode include: + +- Complete compromise of confidentiality +- Potential loss of message authentication +- Violation of the security guarantees provided by the encryption algorithm +- Exposure of sensitive data even when using strong encryption algorithms + +## 11.3.2. What is the potential impact? + +Reusing initialization vectors in counter mode encryption creates critical security vulnerabilities: + +- **Confidentiality breach**: Attackers can XOR two ciphertexts encrypted with the same IV and key to reveal patterns in the plaintext +- **Authentication forgery**: In authenticated encryption modes like GCM, IV reuse can allow attackers to create valid forged messages +- **Key recovery**: In some scenarios, repeated IV usage can lead to recovery of the encryption key itself +- **Complete system compromise**: Once the encryption is broken, all data encrypted with that key becomes vulnerable + +## 11.3.3. How can I fix this? + +Generate cryptographically secure random initialization vectors for each encryption operation and ensure they are never reused with the same key. + +### 11.3.3.1 AES-GCM Encryption Example + +**Non-compliant code:** + +```ballerina +public function encryptData(string data) returns byte[]|error { + byte[16] initialVector = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16]; + byte[16] key = [16, 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1]; + byte[] dataBytes = data.toBytes(); + return crypto:encryptAesGcm(dataBytes, key, initialVector); +} +``` + +In this non-compliant example, the initialization vector is hardcoded, meaning every encryption operation uses the same IV. This completely undermines the security of AES-GCM encryption, regardless of key strength. + +**Compliant code:** + +```ballerina +import ballerina/crypto; +import ballerina/random; + +public function encryptData(string data) returns [byte[], byte[16]]|error { + byte[16] initialVector = []; + foreach int i in 0...15 { + initialVector[i] = (check random:createIntInRange(0, 255)); + } + byte[16] key = [16, 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1]; + byte[] dataBytes = data.toBytes(); + byte[] encryptedData = check crypto:encryptAesGcm(dataBytes, key, initialVector); + return [encryptedData, initialVector]; +} +``` + +This compliant approach generates a cryptographically secure random initialization vector for each encryption operation and returns it along with the encrypted data. The IV must be stored alongside the encrypted data (but doesn't need to be kept secret) to allow for decryption later. + +### 11.3.3.2 AES-CCM Encryption Example + +**Non-compliant code:** + +```ballerina +public function encryptMessage(string message) returns byte[]|error { + // Static nonce - this is vulnerable! + byte[12] nonce = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1]; + byte[16] key = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16]; + byte[] messageBytes = message.toBytes(); + return crypto:encryptAesCcm(messageBytes, key, nonce); +} +``` + +**Compliant code:** + +```ballerina +import ballerina/crypto; +import ballerina/random; + +public function encryptMessage(string message) returns [byte[], byte[12]]|error { + // Generate unique nonce for each encryption + byte[12] nonce = []; + foreach int i in 0...11 { + nonce[i] = (check random:createIntInRange(0, 255)); + } + byte[16] key = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16]; + byte[] messageBytes = message.toBytes(); + byte[] encryptedData = check crypto:encryptAesCcm(messageBytes, key, nonce); + return [encryptedData, nonce]; +} +``` + +## 11.3.4 Additional Resources + +- OWASP - [Top 10 2021 Category A2 - Cryptographic Failures](https://owasp.org/Top10/A02_2021-Cryptographic_Failures/) +- OWASP - [Top 10 2017 Category A3 - Sensitive Data Exposure](https://owasp.org/www-project-top-ten/2017/A3_2017-Sensitive_Data_Exposure) +- OWASP - [Mobile AppSec Verification Standard - Cryptography Requirements](https://mas.owasp.org/checklists/MASVS-CRYPTO/) +- OWASP - [Mobile Top 10 2016 Category M5 - Insufficient Cryptography](https://owasp.org/www-project-mobile-top-10/2016-risks/m5-insufficient-cryptography) +- OWASP - [Mobile Top 10 2024 Category M10 - Insufficient Cryptography](https://owasp.org/www-project-mobile-top-10/2023-risks/m10-insufficient-cryptography) +- CWE - [CWE-323 - Reusing a Nonce, Key Pair in Encryption](https://cwe.mitre.org/data/definitions/323) +- [NIST, SP-800-38A](https://nvlpubs.nist.gov/nistpubs/Legacy/SP/nistspecialpublication800-38a.pdf) - Recommendation for Block Cipher Modes of Operation +- [NIST, SP-800-38C](https://nvlpubs.nist.gov/nistpubs/Legacy/SP/nistspecialpublication800-38c.pdf) - Recommendation for Block Cipher Modes of Operation: The CCM Mode for Authentication and Confidentiality +- [NIST, SP-800-38D](https://nvlpubs.nist.gov/nistpubs/Legacy/SP/nistspecialpublication800-38d.pdf) - Recommendation for Block Cipher Modes of Operation: Galois/Counter Mode (GCM) and GMAC From eae445893a5984951eb5aab04c60e1724000f681 Mon Sep 17 00:00:00 2001 From: TharmiganK Date: Fri, 17 Oct 2025 18:46:03 +0530 Subject: [PATCH 03/10] Update changelog --- changelog.md | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/changelog.md b/changelog.md index c2787a4e..9ddba451 100644 --- a/changelog.md +++ b/changelog.md @@ -7,14 +7,21 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), ### Added - [Introduce support for PBKDF2 password hashing and verification](https://github.com/ballerina-platform/ballerina-lang/issues/43926) +- [Add static analysis rule - Encryption algorithms should be used with secure mode and padding scheme](https://github.com/ballerina-platform/ballerina-library/issues/7940) +- [Add static analysis rule - Passwords should not be stored in plaintext or with a fast hashing algorithm](https://github.com/ballerina-platform/ballerina-library/issues/7950) +- [Add static analysis rule - Counter Mode initialization vectors should not be reused](https://github.com/ballerina-platform/ballerina-library/issues/8010) -### Changed -- [Update OIDS of NIST approved post quantum algorithms](https://github.com/ballerina-platform/ballerina-library/issues/7678) -- [Optimize hardcoded IV detection using semantic model reference counting](https://github.com/ballerina-platform/ballerina-library/issues/8257) +## [2.9.1] - 2025-09-29 ### Fixed - [Implement optional close method check for BStream](https://github.com/ballerina-platform/ballerina-library/issues/8288) +## [2.9.0] - 2025-03-12 + +### Changed +- [Update OIDS of NIST approved post quantum algorithms](https://github.com/ballerina-platform/ballerina-library/issues/7678) + + ## [2.8.0] - 2025-02-11 ### Added From ba004dc55a969cf59642d115570c58c2b2dfc309 Mon Sep 17 00:00:00 2001 From: TharmiganK Date: Fri, 17 Oct 2025 19:16:35 +0530 Subject: [PATCH 04/10] Address sonar cloud reported issues --- .../CryptoAnalyzerUtils.java | 156 ++++++++++-------- .../staticcodeanalyzer/FunctionContext.java | 4 +- 2 files changed, 91 insertions(+), 69 deletions(-) diff --git a/compiler-plugin/src/main/java/io/ballerina/stdlib/crypto/compiler/staticcodeanalyzer/CryptoAnalyzerUtils.java b/compiler-plugin/src/main/java/io/ballerina/stdlib/crypto/compiler/staticcodeanalyzer/CryptoAnalyzerUtils.java index 05486b15..261cfaa7 100644 --- a/compiler-plugin/src/main/java/io/ballerina/stdlib/crypto/compiler/staticcodeanalyzer/CryptoAnalyzerUtils.java +++ b/compiler-plugin/src/main/java/io/ballerina/stdlib/crypto/compiler/staticcodeanalyzer/CryptoAnalyzerUtils.java @@ -65,6 +65,11 @@ public final class CryptoAnalyzerUtils { private static final String BALLERINA_ORG = "ballerina"; private static final String CRYPTO = "crypto"; + // Private constructor to prevent instantiation + private CryptoAnalyzerUtils() { + + } + /** * Retrieves the FunctionSymbol for a given FunctionCallExpressionNode if it belongs to the Ballerina * crypto module. @@ -228,73 +233,21 @@ public static Map getModuleLevelVarExpressions(ModulePar } /** - * Adds variable declarations and assignments from the given block node - * to the provided map until the specified statement node is reached. + * Collects variable declarations and assignments from the given block node + * up to the specified statement node. * * @param blockNode the block node (FunctionBodyBlockNode or BlockStatementNode) * @param statementNode the statement node to stop at * @param varExpressions the map to store variable names and their expressions */ - public static void addVariableDeclarationsUntil(Node blockNode, StatementNode statementNode, - Map varExpressions) { - NodeList statements; - if (blockNode instanceof FunctionBodyBlockNode functionBody) { - statements = functionBody.statements(); - } else if (blockNode instanceof BlockStatementNode blockStatementNode) { - statements = blockStatementNode.statements(); - } else { - // Any other node type is unsupported - throw new IllegalArgumentException("Unsupported block node type: " + blockNode.kind()); - } - addVarExpressionsUtil(statements, statementNode, varExpressions); - } - - /** - * Utility method to add variable declarations and assignments from a list of statements - * to the provided map until the specified target statement is reached. - * - * @param statements the list of statements - * @param targetStatement the target statement to stop at - * @param varExpressions the map to store variable names and their expressions - */ - public static void addVarExpressionsUtil(NodeList statements, StatementNode targetStatement, - Map varExpressions) { - for (StatementNode statement : statements) { - // If we find any block nodes in the middle we cannot verify the variable declarations or assignments - // since they may be changed within those blocks. It will become complex if there are conditional blocks - // etc. So we stop the analysis at that point and remove all collected variable expressions. - if (isBlockStatementNode(statement)) { - // Clean the collected variable expressions as we cannot guarantee their validity beyond this point - varExpressions.clear(); - break; - } - - if (statement.equals(targetStatement)) { - break; - } - - if (statement instanceof AssignmentStatementNode assignmentNode) { - Node varRef = assignmentNode.varRef(); - if (!(varRef instanceof IdentifierToken variableNameIdentifier)) { - continue; - } - String varName = variableNameIdentifier.text(); - varName = unescapeIdentifier(varName); - ExpressionNode expression = assignmentNode.expression(); - varExpressions.put(varName, expression); - } else if (statement instanceof VariableDeclarationNode variableDeclarationNode) { - BindingPatternNode bindingPatternNode = variableDeclarationNode.typedBindingPattern().bindingPattern(); - // Only supporting capture binding patterns for variable declarations - if (variableDeclarationNode.initializer().isEmpty() || - !(bindingPatternNode instanceof CaptureBindingPatternNode captureBindingPattern)) { - break; - } - String varName = captureBindingPattern.variableName().text(); - varName = unescapeIdentifier(varName); - ExpressionNode initializer = variableDeclarationNode.initializer().get(); - varExpressions.put(varName, initializer); - } - } + public static void collectVariableExpressionsUntilStatement(Node blockNode, StatementNode statementNode, + Map varExpressions) { + NodeList statements = switch (blockNode) { + case FunctionBodyBlockNode functionBody -> functionBody.statements(); + case BlockStatementNode blockStatementNode -> blockStatementNode.statements(); + default -> throw new IllegalArgumentException("Unsupported block node type: " + blockNode.kind()); + }; + processStatementsForVariableExpressions(statements, statementNode, varExpressions); } /** @@ -335,13 +288,82 @@ public static Optional getStringValue(String key, FunctionContext contex } else if (valueExpr instanceof NameReferenceNode refNode) { // Checking for constant values Optional refSymbol = context.semanticModel().symbol(refNode); - if (refSymbol.isPresent() && refSymbol.get() instanceof ConstantSymbol constantRef) { - if (constantRef.constValue() instanceof ConstantValue constantValue && - constantValue.value() instanceof String constString) { - return Optional.of(constString); - } + if (refSymbol.isPresent() && refSymbol.get() instanceof ConstantSymbol constantRef && + constantRef.constValue() instanceof ConstantValue constantValue && + constantValue.value() instanceof String constString) { + return Optional.of(constString); } } return Optional.empty(); } + + /** + * Processes statements to collect variable declarations and assignments + * up to the specified target statement. + * + * @param statements the list of statements + * @param targetStatement the target statement to stop at + * @param varExpressions the map to store variable names and their expressions + */ + public static void processStatementsForVariableExpressions(NodeList statements, + StatementNode targetStatement, + Map varExpressions) { + for (StatementNode statement : statements) { + boolean isBlockStatement = isBlockStatementNode(statement); + + // Stop processing if we reach the target statement or found a block statement + if (statement.equals(targetStatement) || isBlockStatement) { + if (isBlockStatement) { + // If we find any block nodes, we cannot verify variable declarations or assignments + // since they may be changed within those blocks. Clear collected expressions and stop. + varExpressions.clear(); + } + break; + } + + // Process assignment statements + if (statement instanceof AssignmentStatementNode assignmentNode) { + processAssignmentStatement(assignmentNode, varExpressions); + } else if (statement instanceof VariableDeclarationNode variableDeclarationNode) { + processVariableDeclaration(variableDeclarationNode, varExpressions); + } + } + } + + /** + * Processes an assignment statement and adds it to the variable expressions map. + * + * @param assignmentNode the assignment statement node + * @param varExpressions the map to store variable names and their expressions + */ + private static void processAssignmentStatement(AssignmentStatementNode assignmentNode, + Map varExpressions) { + Node varRef = assignmentNode.varRef(); + if (varRef instanceof IdentifierToken variableNameIdentifier) { + String varName = unescapeIdentifier(variableNameIdentifier.text()); + ExpressionNode expression = assignmentNode.expression(); + varExpressions.put(varName, expression); + } + } + + /** + * Processes a variable declaration statement and adds it to the variable expressions map. + * + * @param variableDeclarationNode the variable declaration node + * @param varExpressions the map to store variable names and their expressions + */ + private static void processVariableDeclaration(VariableDeclarationNode variableDeclarationNode, + Map varExpressions) { + BindingPatternNode bindingPatternNode = variableDeclarationNode.typedBindingPattern().bindingPattern(); + + // Only supporting capture binding patterns for variable declarations + if (variableDeclarationNode.initializer().isEmpty() || + !(bindingPatternNode instanceof CaptureBindingPatternNode captureBindingPattern)) { + return; + } + + String varName = unescapeIdentifier(captureBindingPattern.variableName().text()); + ExpressionNode initializer = variableDeclarationNode.initializer().get(); + varExpressions.put(varName, initializer); + } } diff --git a/compiler-plugin/src/main/java/io/ballerina/stdlib/crypto/compiler/staticcodeanalyzer/FunctionContext.java b/compiler-plugin/src/main/java/io/ballerina/stdlib/crypto/compiler/staticcodeanalyzer/FunctionContext.java index 9f295bc3..18ef6340 100644 --- a/compiler-plugin/src/main/java/io/ballerina/stdlib/crypto/compiler/staticcodeanalyzer/FunctionContext.java +++ b/compiler-plugin/src/main/java/io/ballerina/stdlib/crypto/compiler/staticcodeanalyzer/FunctionContext.java @@ -37,7 +37,7 @@ import java.util.Map; import java.util.Optional; -import static io.ballerina.stdlib.crypto.compiler.staticcodeanalyzer.CryptoAnalyzerUtils.addVariableDeclarationsUntil; +import static io.ballerina.stdlib.crypto.compiler.staticcodeanalyzer.CryptoAnalyzerUtils.collectVariableExpressionsUntilStatement; import static io.ballerina.stdlib.crypto.compiler.staticcodeanalyzer.CryptoAnalyzerUtils.getModuleLevelVarExpressions; import static io.ballerina.stdlib.crypto.compiler.staticcodeanalyzer.CryptoAnalyzerUtils.getModulePartNode; import static io.ballerina.stdlib.crypto.compiler.staticcodeanalyzer.CryptoAnalyzerUtils.getParamExpressions; @@ -114,7 +114,7 @@ public static FunctionContext getInstance(SemanticModel semanticModel, Reporter } // Add variable declarations up to the function call statement - addVariableDeclarationsUntil(functionBodyOpt.get(), statementNode.get(), varExpressions); + collectVariableExpressionsUntilStatement(functionBodyOpt.get(), statementNode.get(), varExpressions); return new FunctionContext(semanticModel, reporter, document, functionName, location, paramExpressions, varExpressions); } From e6c34378095eba577aa54c224d1d052323b00dd7 Mon Sep 17 00:00:00 2001 From: TharmiganK Date: Mon, 20 Oct 2025 08:21:27 +0530 Subject: [PATCH 05/10] Add enable code coverage report for compiler plugin --- compiler-plugin-tests/build.gradle | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/compiler-plugin-tests/build.gradle b/compiler-plugin-tests/build.gradle index 24d488c6..454347e0 100644 --- a/compiler-plugin-tests/build.gradle +++ b/compiler-plugin-tests/build.gradle @@ -20,6 +20,7 @@ plugins { id 'java' id 'checkstyle' id 'com.github.spotbugs' + id 'jacoco' } description = 'Ballerina - Crypto Compiler Plugin Tests' @@ -61,6 +62,18 @@ test { } } } + jacoco { + destinationFile = file("$buildDir/jacoco/test.exec") + } + finalizedBy jacocoTestReport +} + +jacocoTestReport { + dependsOn test + reports { + xml.required = true + } + sourceSets project(':crypto-compiler-plugin').sourceSets.main } spotbugsTest { From 7e96077da36bfe1460edaf40108505c7b947ee18 Mon Sep 17 00:00:00 2001 From: TharmiganK Date: Mon, 20 Oct 2025 08:23:16 +0530 Subject: [PATCH 06/10] Bump to the next minor version --- gradle.properties | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle.properties b/gradle.properties index 02accd8e..30dc7192 100644 --- a/gradle.properties +++ b/gradle.properties @@ -1,6 +1,6 @@ org.gradle.caching=true group=io.ballerina.stdlib -version=2.9.1-SNAPSHOT +version=2.10.0-SNAPSHOT checkstylePluginVersion=10.12.0 bouncycastleVersion=1.80 spotbugsPluginVersion=6.0.18 From 7d7895b2890468ed72885e5c90bebd055f1f9061 Mon Sep 17 00:00:00 2001 From: TharmiganK Date: Mon, 20 Oct 2025 08:25:35 +0530 Subject: [PATCH 07/10] [Automated] Update the native jar versions --- ballerina/Ballerina.toml | 6 +++--- ballerina/CompilerPlugin.toml | 2 +- ballerina/Dependencies.toml | 2 +- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/ballerina/Ballerina.toml b/ballerina/Ballerina.toml index 6ca89f60..00c6116e 100644 --- a/ballerina/Ballerina.toml +++ b/ballerina/Ballerina.toml @@ -1,7 +1,7 @@ [package] org = "ballerina" name = "crypto" -version = "2.9.1" +version = "2.10.0" authors = ["Ballerina"] keywords = ["security", "hash", "hmac", "sign", "encrypt", "decrypt", "private key", "public key"] repository = "https://github.com/ballerina-platform/module-ballerina-crypto" @@ -15,8 +15,8 @@ graalvmCompatible = true [[platform.java21.dependency]] groupId = "io.ballerina.stdlib" artifactId = "crypto-native" -version = "2.9.1" -path = "../native/build/libs/crypto-native-2.9.1-SNAPSHOT.jar" +version = "2.10.0" +path = "../native/build/libs/crypto-native-2.10.0-SNAPSHOT.jar" [[platform.java21.dependency]] groupId = "org.bouncycastle" diff --git a/ballerina/CompilerPlugin.toml b/ballerina/CompilerPlugin.toml index 2e2112d3..738a1475 100644 --- a/ballerina/CompilerPlugin.toml +++ b/ballerina/CompilerPlugin.toml @@ -3,4 +3,4 @@ id = "crypto-compiler-plugin" class = "io.ballerina.stdlib.crypto.compiler.CryptoCompilerPlugin" [[dependency]] -path = "../compiler-plugin/build/libs/crypto-compiler-plugin-2.9.1-SNAPSHOT.jar" +path = "../compiler-plugin/build/libs/crypto-compiler-plugin-2.10.0-SNAPSHOT.jar" diff --git a/ballerina/Dependencies.toml b/ballerina/Dependencies.toml index 6488f734..46534761 100644 --- a/ballerina/Dependencies.toml +++ b/ballerina/Dependencies.toml @@ -10,7 +10,7 @@ distribution-version = "2201.12.0" [[package]] org = "ballerina" name = "crypto" -version = "2.9.1" +version = "2.10.0" dependencies = [ {org = "ballerina", name = "io"}, {org = "ballerina", name = "jballerina.java"}, From b673aedfab8d85e4b6047b4f80c44f921f4cc879 Mon Sep 17 00:00:00 2001 From: TharmiganK Date: Mon, 20 Oct 2025 10:15:20 +0530 Subject: [PATCH 08/10] Improve code coverage --- .../StaticCodeAnalyzerTest.java | 114 +++++--- .../ballerina_packages/rule1/aes_cbc.bal | 2 +- .../rule1/aes_cbc_as_import.bal | 2 +- .../ballerina_packages/rule1/aes_ecb.bal | 2 +- .../rule1/aes_ecb_as_import.bal | 2 +- .../rule1/weak_cipher_algo.bal | 46 ++++ .../ballerina_packages/rule2/argon_func.bal | 42 +++ .../rule2/argon_func_var_named_arg.bal | 2 +- .../rule2/argon_func_var_pos_arg.bal | 2 +- .../rule2/argon_inline_named_arg.bal | 2 +- .../rule2/argon_inline_pos_arg.bal | 2 +- .../rule2/argon_mod_var_named_arg.bal | 2 +- .../rule2/argon_mod_var_pos_arg.bal | 2 +- .../rule2/bcrypt_func_var_named_arg.bal | 2 +- .../rule2/bcrypt_func_var_pos_arg.bal | 2 +- .../rule2/bcrypt_inline_named_arg.bal | 2 +- .../rule2/bcrypt_inline_pos_arg.bal | 2 +- .../rule2/bcrypt_mod_var_named_arg.bal | 2 +- .../rule2/bcrypt_mod_var_pos_arg.bal | 2 +- .../rule2/modules/module/module.bal | 20 ++ .../ballerina_packages/rule2/pbkdf2_func.bal | 42 +++ .../rule2/pbkdf2_func_var_named_arg.bal | 2 +- .../rule2/pbkdf2_func_var_pos_arg.bal | 2 +- .../rule2/pbkdf2_inline_named_arg.bal | 2 +- .../rule2/pbkdf2_inline_pos_arg.bal | 2 +- .../rule2/pbkdf2_mod_var_named_arg.bal | 2 +- .../rule2/pbkdf2_mod_var_pos_arg.bal | 2 +- .../rule3/func_hardcoded_iv_param.bal | 61 ++++ .../rule3/func_var_named_arg.bal | 2 +- .../rule3/func_var_pos_arg.bal | 2 +- .../rule3/inline_named_arg.bal | 2 +- .../rule3/inline_pos_arg.bal | 2 +- .../rule3/mod_var_named_arg.bal | 2 +- .../rule3/mod_var_pos_arg.bal | 2 +- .../rule3/modules/module/module.bal | 19 ++ .../expected_output/rule1.json | 160 ++++++++--- .../expected_output/rule2.json | 260 ++++++++++++++++++ .../expected_output/rule3.json | 160 +++++++++++ .../CryptoAnalyzerUtils.java | 6 +- .../staticcodeanalyzer/FunctionContext.java | 2 +- .../AvoidFastHashAlgorithmsRule.java | 14 +- .../AvoidReusingCounterModeVectorsRule.java | 36 ++- docs/spec/spec.md | 12 +- 43 files changed, 929 insertions(+), 121 deletions(-) create mode 100644 compiler-plugin-tests/src/test/resources/static_code_analyzer/ballerina_packages/rule1/weak_cipher_algo.bal create mode 100644 compiler-plugin-tests/src/test/resources/static_code_analyzer/ballerina_packages/rule2/argon_func.bal create mode 100644 compiler-plugin-tests/src/test/resources/static_code_analyzer/ballerina_packages/rule2/modules/module/module.bal create mode 100644 compiler-plugin-tests/src/test/resources/static_code_analyzer/ballerina_packages/rule2/pbkdf2_func.bal create mode 100644 compiler-plugin-tests/src/test/resources/static_code_analyzer/ballerina_packages/rule3/func_hardcoded_iv_param.bal create mode 100644 compiler-plugin-tests/src/test/resources/static_code_analyzer/ballerina_packages/rule3/modules/module/module.bal diff --git a/compiler-plugin-tests/src/test/java/io/ballerina/stdlib/crypto/compiler/staticcodeanalyzer/StaticCodeAnalyzerTest.java b/compiler-plugin-tests/src/test/java/io/ballerina/stdlib/crypto/compiler/staticcodeanalyzer/StaticCodeAnalyzerTest.java index 3391cc15..a7374a5a 100644 --- a/compiler-plugin-tests/src/test/java/io/ballerina/stdlib/crypto/compiler/staticcodeanalyzer/StaticCodeAnalyzerTest.java +++ b/compiler-plugin-tests/src/test/java/io/ballerina/stdlib/crypto/compiler/staticcodeanalyzer/StaticCodeAnalyzerTest.java @@ -120,70 +120,122 @@ private void validateRules(List rules) { } private void validateIssues(CryptoRule rule, List issues) { + int index; switch (rule) { case AVOID_WEAK_CIPHER_ALGORITHMS: - Assert.assertEquals(issues.size(), 4); - Assertions.assertIssue(issues, 0, "ballerina/crypto:1", "aes_cbc.bal", + index = 0; + Assert.assertEquals(issues.size(), 10); + Assertions.assertIssue(issues, index++, "ballerina/crypto:1", "aes_cbc.bal", 30, 30, Source.BUILT_IN); - Assertions.assertIssue(issues, 1, "ballerina/crypto:1", "aes_cbc_as_import.bal", + Assertions.assertIssue(issues, index++, "ballerina/crypto:1", "aes_cbc_as_import.bal", 30, 30, Source.BUILT_IN); - Assertions.assertIssue(issues, 2, "ballerina/crypto:1", "aes_ecb.bal", + Assertions.assertIssue(issues, index++, "ballerina/crypto:1", "aes_ecb.bal", 26, 26, Source.BUILT_IN); - Assertions.assertIssue(issues, 3, "ballerina/crypto:1", "aes_ecb_as_import.bal", + Assertions.assertIssue(issues, index++, "ballerina/crypto:1", "aes_ecb_as_import.bal", 26, 26, Source.BUILT_IN); + Assertions.assertIssue(issues, index++, "ballerina/crypto:1", "weak_cipher_algo.bal", + 23, 23, Source.BUILT_IN); + Assertions.assertIssue(issues, index++, "ballerina/crypto:1", "weak_cipher_algo.bal", + 27, 27, Source.BUILT_IN); + Assertions.assertIssue(issues, index++, "ballerina/crypto:3", "weak_cipher_algo.bal", + 27, 27, Source.BUILT_IN); + Assertions.assertIssue(issues, index++, "ballerina/crypto:1", "weak_cipher_algo.bal", + 35, 35, Source.BUILT_IN); + Assertions.assertIssue(issues, index++, "ballerina/crypto:1", "weak_cipher_algo.bal", + 36, 36, Source.BUILT_IN); + Assertions.assertIssue(issues, index, "ballerina/crypto:1", "weak_cipher_algo.bal", + 39, 39, Source.BUILT_IN); break; case AVOID_FAST_HASH_ALGORITHMS: - Assert.assertEquals(issues.size(), 18); - Assertions.assertIssue(issues, 0, "ballerina/crypto:2", "argon_func_var_named_arg.bal", + Assert.assertEquals(issues.size(), 29); + index = 0; + Assertions.assertIssue(issues, index++, "ballerina/crypto:2", "argon_func.bal", + 31, 31, Source.BUILT_IN); + Assertions.assertIssue(issues, index++, "ballerina/crypto:2", "argon_func.bal", + 33, 33, Source.BUILT_IN); + Assertions.assertIssue(issues, index++, "ballerina/crypto:2", "argon_func.bal", + 35, 35, Source.BUILT_IN); + Assertions.assertIssue(issues, index++, "ballerina/crypto:2", "argon_func.bal", + 37, 37, Source.BUILT_IN); + Assertions.assertIssue(issues, index++, "ballerina/crypto:2", "argon_func_var_named_arg.bal", 23, 23, Source.BUILT_IN); - Assertions.assertIssue(issues, 1, "ballerina/crypto:2", "argon_func_var_pos_arg.bal", + Assertions.assertIssue(issues, index++, "ballerina/crypto:2", "argon_func_var_pos_arg.bal", 23, 23, Source.BUILT_IN); - Assertions.assertIssue(issues, 2, "ballerina/crypto:2", "argon_inline_named_arg.bal", + Assertions.assertIssue(issues, index++, "ballerina/crypto:2", "argon_inline_named_arg.bal", 20, 20, Source.BUILT_IN); - Assertions.assertIssue(issues, 3, "ballerina/crypto:2", "argon_inline_pos_arg.bal", + Assertions.assertIssue(issues, index++, "ballerina/crypto:2", "argon_inline_pos_arg.bal", 20, 20, Source.BUILT_IN); - Assertions.assertIssue(issues, 4, "ballerina/crypto:2", "argon_mod_var_named_arg.bal", + Assertions.assertIssue(issues, index++, "ballerina/crypto:2", "argon_mod_var_named_arg.bal", 24, 24, Source.BUILT_IN); - Assertions.assertIssue(issues, 5, "ballerina/crypto:2", "argon_mod_var_pos_arg.bal", + Assertions.assertIssue(issues, index++, "ballerina/crypto:2", "argon_mod_var_pos_arg.bal", 24, 24, Source.BUILT_IN); - Assertions.assertIssue(issues, 6, "ballerina/crypto:2", "bcrypt_func_var_named_arg.bal", + Assertions.assertIssue(issues, index++, "ballerina/crypto:2", "bcrypt_func_var_named_arg.bal", 21, 21, Source.BUILT_IN); - Assertions.assertIssue(issues, 7, "ballerina/crypto:2", "bcrypt_func_var_pos_arg.bal", + Assertions.assertIssue(issues, index++, "ballerina/crypto:2", "bcrypt_func_var_pos_arg.bal", 21, 21, Source.BUILT_IN); - Assertions.assertIssue(issues, 8, "ballerina/crypto:2", "bcrypt_inline_named_arg.bal", + Assertions.assertIssue(issues, index++, "ballerina/crypto:2", "bcrypt_inline_named_arg.bal", 20, 20, Source.BUILT_IN); - Assertions.assertIssue(issues, 9, "ballerina/crypto:2", "bcrypt_inline_pos_arg.bal", + Assertions.assertIssue(issues, index++, "ballerina/crypto:2", "bcrypt_inline_pos_arg.bal", 20, 20, Source.BUILT_IN); - Assertions.assertIssue(issues, 10, "ballerina/crypto:2", "bcrypt_mod_var_named_arg.bal", + Assertions.assertIssue(issues, index++, "ballerina/crypto:2", "bcrypt_mod_var_named_arg.bal", 22, 22, Source.BUILT_IN); - Assertions.assertIssue(issues, 11, "ballerina/crypto:2", "bcrypt_mod_var_pos_arg.bal", + Assertions.assertIssue(issues, index++, "ballerina/crypto:2", "bcrypt_mod_var_pos_arg.bal", 22, 22, Source.BUILT_IN); - Assertions.assertIssue(issues, 12, "ballerina/crypto:2", "pbkdf2_func_var_named_arg.bal", + Assertions.assertIssue(issues, index++, "ballerina/crypto:2", "pbkdf2_func.bal", 21, 21, Source.BUILT_IN); - Assertions.assertIssue(issues, 13, "ballerina/crypto:2", "pbkdf2_func_var_pos_arg.bal", + Assertions.assertIssue(issues, index++, "ballerina/crypto:2", "pbkdf2_func.bal", + 22, 22, Source.BUILT_IN); + Assertions.assertIssue(issues, index++, "ballerina/crypto:2", "pbkdf2_func.bal", + 23, 23, Source.BUILT_IN); + Assertions.assertIssue(issues, index++, "ballerina/crypto:2", "pbkdf2_func.bal", + 24, 24, Source.BUILT_IN); + Assertions.assertIssue(issues, index++, "ballerina/crypto:2", "pbkdf2_func.bal", + 38, 38, Source.BUILT_IN); + Assertions.assertIssue(issues, index++, "ballerina/crypto:2", "pbkdf2_func.bal", + 39, 39, Source.BUILT_IN); + Assertions.assertIssue(issues, index++, "ballerina/crypto:2", "pbkdf2_func.bal", + 40, 40, Source.BUILT_IN); + Assertions.assertIssue(issues, index++, "ballerina/crypto:2", "pbkdf2_func_var_named_arg.bal", 21, 21, Source.BUILT_IN); - Assertions.assertIssue(issues, 14, "ballerina/crypto:2", "pbkdf2_inline_named_arg.bal", + Assertions.assertIssue(issues, index++, "ballerina/crypto:2", "pbkdf2_func_var_pos_arg.bal", + 21, 21, Source.BUILT_IN); + Assertions.assertIssue(issues, index++, "ballerina/crypto:2", "pbkdf2_inline_named_arg.bal", 20, 20, Source.BUILT_IN); - Assertions.assertIssue(issues, 15, "ballerina/crypto:2", "pbkdf2_inline_pos_arg.bal", + Assertions.assertIssue(issues, index++, "ballerina/crypto:2", "pbkdf2_inline_pos_arg.bal", 20, 20, Source.BUILT_IN); - Assertions.assertIssue(issues, 16, "ballerina/crypto:2", "pbkdf2_mod_var_named_arg.bal", + Assertions.assertIssue(issues, index++, "ballerina/crypto:2", "pbkdf2_mod_var_named_arg.bal", 22, 22, Source.BUILT_IN); - Assertions.assertIssue(issues, 17, "ballerina/crypto:2", "pbkdf2_mod_var_pos_arg.bal", + Assertions.assertIssue(issues, index, "ballerina/crypto:2", "pbkdf2_mod_var_pos_arg.bal", 22, 22, Source.BUILT_IN); break; case AVOID_REUSING_COUNTER_MODE_VECTORS: - Assert.assertEquals(issues.size(), 6); - Assertions.assertIssue(issues, 0, "ballerina/crypto:3", "func_var_named_arg.bal", + Assert.assertEquals(issues.size(), 13); + index = 0; + Assertions.assertIssue(issues, index++, "ballerina/crypto:3", "func_hardcoded_iv_param.bal", + 24, 24, Source.BUILT_IN); + Assertions.assertIssue(issues, index++, "ballerina/crypto:3", "func_hardcoded_iv_param.bal", + 26, 26, Source.BUILT_IN); + Assertions.assertIssue(issues, index++, "ballerina/crypto:3", "func_hardcoded_iv_param.bal", + 29, 29, Source.BUILT_IN); + Assertions.assertIssue(issues, index++, "ballerina/crypto:3", "func_hardcoded_iv_param.bal", + 32, 32, Source.BUILT_IN); + Assertions.assertIssue(issues, index++, "ballerina/crypto:3", "func_hardcoded_iv_param.bal", + 36, 36, Source.BUILT_IN); + Assertions.assertIssue(issues, index++, "ballerina/crypto:3", "func_hardcoded_iv_param.bal", + 39, 39, Source.BUILT_IN); + Assertions.assertIssue(issues, index++, "ballerina/crypto:3", "func_hardcoded_iv_param.bal", + 42, 42, Source.BUILT_IN); + Assertions.assertIssue(issues, index++, "ballerina/crypto:3", "func_var_named_arg.bal", 22, 22, Source.BUILT_IN); - Assertions.assertIssue(issues, 1, "ballerina/crypto:3", "func_var_pos_arg.bal", + Assertions.assertIssue(issues, index++, "ballerina/crypto:3", "func_var_pos_arg.bal", 22, 22, Source.BUILT_IN); - Assertions.assertIssue(issues, 2, "ballerina/crypto:3", "inline_named_arg.bal", + Assertions.assertIssue(issues, index++, "ballerina/crypto:3", "inline_named_arg.bal", 21, 21, Source.BUILT_IN); - Assertions.assertIssue(issues, 3, "ballerina/crypto:3", "inline_pos_arg.bal", + Assertions.assertIssue(issues, index++, "ballerina/crypto:3", "inline_pos_arg.bal", 21, 21, Source.BUILT_IN); - Assertions.assertIssue(issues, 4, "ballerina/crypto:3", "mod_var_named_arg.bal", + Assertions.assertIssue(issues, index++, "ballerina/crypto:3", "mod_var_named_arg.bal", 23, 23, Source.BUILT_IN); - Assertions.assertIssue(issues, 5, "ballerina/crypto:3", "mod_var_pos_arg.bal", + Assertions.assertIssue(issues, index, "ballerina/crypto:3", "mod_var_pos_arg.bal", 23, 23, Source.BUILT_IN); break; default: diff --git a/compiler-plugin-tests/src/test/resources/static_code_analyzer/ballerina_packages/rule1/aes_cbc.bal b/compiler-plugin-tests/src/test/resources/static_code_analyzer/ballerina_packages/rule1/aes_cbc.bal index e8391166..cd9e17ef 100644 --- a/compiler-plugin-tests/src/test/resources/static_code_analyzer/ballerina_packages/rule1/aes_cbc.bal +++ b/compiler-plugin-tests/src/test/resources/static_code_analyzer/ballerina_packages/rule1/aes_cbc.bal @@ -1,4 +1,4 @@ -// Copyright (c) 2025 WSO2 LLC. (http://www.wso2.org) +// Copyright (c) 2025 WSO2 LLC. (http://www.wso2.com) // // WSO2 LLC. licenses this file to you under the Apache License, // Version 2.0 (the "License"); you may not use this file except diff --git a/compiler-plugin-tests/src/test/resources/static_code_analyzer/ballerina_packages/rule1/aes_cbc_as_import.bal b/compiler-plugin-tests/src/test/resources/static_code_analyzer/ballerina_packages/rule1/aes_cbc_as_import.bal index 278b1f8a..60c1b325 100644 --- a/compiler-plugin-tests/src/test/resources/static_code_analyzer/ballerina_packages/rule1/aes_cbc_as_import.bal +++ b/compiler-plugin-tests/src/test/resources/static_code_analyzer/ballerina_packages/rule1/aes_cbc_as_import.bal @@ -1,4 +1,4 @@ -// Copyright (c) 2025 WSO2 LLC. (http://www.wso2.org) +// Copyright (c) 2025 WSO2 LLC. (http://www.wso2.com) // // WSO2 LLC. licenses this file to you under the Apache License, // Version 2.0 (the "License"); you may not use this file except diff --git a/compiler-plugin-tests/src/test/resources/static_code_analyzer/ballerina_packages/rule1/aes_ecb.bal b/compiler-plugin-tests/src/test/resources/static_code_analyzer/ballerina_packages/rule1/aes_ecb.bal index b344c0b8..632eca1d 100644 --- a/compiler-plugin-tests/src/test/resources/static_code_analyzer/ballerina_packages/rule1/aes_ecb.bal +++ b/compiler-plugin-tests/src/test/resources/static_code_analyzer/ballerina_packages/rule1/aes_ecb.bal @@ -1,4 +1,4 @@ -// Copyright (c) 2025 WSO2 LLC. (http://www.wso2.org) +// Copyright (c) 2025 WSO2 LLC. (http://www.wso2.com) // // WSO2 LLC. licenses this file to you under the Apache License, // Version 2.0 (the "License"); you may not use this file except diff --git a/compiler-plugin-tests/src/test/resources/static_code_analyzer/ballerina_packages/rule1/aes_ecb_as_import.bal b/compiler-plugin-tests/src/test/resources/static_code_analyzer/ballerina_packages/rule1/aes_ecb_as_import.bal index d4934ab9..d70eb0d0 100644 --- a/compiler-plugin-tests/src/test/resources/static_code_analyzer/ballerina_packages/rule1/aes_ecb_as_import.bal +++ b/compiler-plugin-tests/src/test/resources/static_code_analyzer/ballerina_packages/rule1/aes_ecb_as_import.bal @@ -1,4 +1,4 @@ -// Copyright (c) 2025 WSO2 LLC. (http://www.wso2.org) +// Copyright (c) 2025 WSO2 LLC. (http://www.wso2.com) // // WSO2 LLC. licenses this file to you under the Apache License, // Version 2.0 (the "License"); you may not use this file except diff --git a/compiler-plugin-tests/src/test/resources/static_code_analyzer/ballerina_packages/rule1/weak_cipher_algo.bal b/compiler-plugin-tests/src/test/resources/static_code_analyzer/ballerina_packages/rule1/weak_cipher_algo.bal new file mode 100644 index 00000000..c9c9e3e5 --- /dev/null +++ b/compiler-plugin-tests/src/test/resources/static_code_analyzer/ballerina_packages/rule1/weak_cipher_algo.bal @@ -0,0 +1,46 @@ +// Copyright (c) 2025 WSO2 LLC. (http://www.wso2.com) +// +// WSO2 LLC. licenses this file to you under the Apache License, +// Version 2.0 (the "License"); you may not use this file except +// in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +import ballerina/crypto; + +public function main() returns error? { + byte[] inputBytes = "input".toBytes(); + byte[] keyBytes = "key".toBytes(); + + // Encrypt using AES ECB mode is not secure + _ = check crypto:encryptAesEcb(inputBytes, keyBytes); + + byte[] iv = "1234567890123456".toBytes(); + // Encrypt using AES CBC mode is secure + _ = check crypto:encryptAesCbc(inputBytes, keyBytes, iv); + + crypto:KeyStore keyStore = { + path: "/path/to/keyStore.p12", + password: "keyStorePassword" + }; + crypto:PublicKey publicKey = check crypto:decodeRsaPublicKeyFromTrustStore(keyStore, "keyAlias"); + // Encrypt using RSA ECB with PKCS1 padding is not secure + _ = check crypto:encryptRsaEcb(inputBytes, publicKey, crypto:PKCS1); + _ = check crypto:encryptRsaEcb(inputBytes, publicKey, "PKCS1"); + + // Default RSA ECB encryption uses PKCS1 padding. Hence, it is not secure + _ = check crypto:encryptRsaEcb(inputBytes, publicKey); + + // Encrypt using RSA ECB with OAEP padding is secure + _ = check crypto:encryptRsaEcb(inputBytes, publicKey, crypto:OAEPWithSHA1AndMGF1); + _ = check crypto:encryptRsaEcb(inputBytes, publicKey, "OAEPWithSHA1AndMGF1"); + +} diff --git a/compiler-plugin-tests/src/test/resources/static_code_analyzer/ballerina_packages/rule2/argon_func.bal b/compiler-plugin-tests/src/test/resources/static_code_analyzer/ballerina_packages/rule2/argon_func.bal new file mode 100644 index 00000000..61fb385a --- /dev/null +++ b/compiler-plugin-tests/src/test/resources/static_code_analyzer/ballerina_packages/rule2/argon_func.bal @@ -0,0 +1,42 @@ +// Copyright (c) 2025 WSO2 LLC. (http://www.wso2.com) +// +// WSO2 LLC. licenses this file to you under the Apache License, +// Version 2.0 (the "License"); you may not use this file except +// in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +import ballerina/crypto; +import wso2/rule2.'module as mod; + +const MEMORY = 16384; + +public function ArgonFunc() returns error? { + // Default parameters - iterations: 3, memory: 65536, parallelism: 4 + _ = check crypto:hashArgon2("password"); + // Custom least secure parameters: iterations: 2, memory: 19456, parallelism: 1 + _ = check crypto:hashArgon2("password", 2, 19456, 1); + // Secure parameters - memory constant from different module + _ = check crypto:hashArgon2("password", memory = mod:MINIMUM_ALLOWED_MEMORY); + + // Unsecure parameters + // iterations + _ = check crypto:hashArgon2("password", 1); + // memory + _ = check crypto:hashArgon2("password", memory = 8192); + // memory with constant + _ = check crypto:hashArgon2("password", memory = MEMORY); + // memory with module constant + _ = check crypto:hashArgon2("password", memory = mod:MEMORY); + // memory with module variable - This is a negative test case + // Cannot be determined at compile time + _ = check crypto:hashArgon2("password", memory = mod:memory); +} diff --git a/compiler-plugin-tests/src/test/resources/static_code_analyzer/ballerina_packages/rule2/argon_func_var_named_arg.bal b/compiler-plugin-tests/src/test/resources/static_code_analyzer/ballerina_packages/rule2/argon_func_var_named_arg.bal index 94c76b34..60c58d92 100644 --- a/compiler-plugin-tests/src/test/resources/static_code_analyzer/ballerina_packages/rule2/argon_func_var_named_arg.bal +++ b/compiler-plugin-tests/src/test/resources/static_code_analyzer/ballerina_packages/rule2/argon_func_var_named_arg.bal @@ -1,4 +1,4 @@ -// Copyright (c) 2025 WSO2 LLC. (http://www.wso2.org) +// Copyright (c) 2025 WSO2 LLC. (http://www.wso2.com) // // WSO2 LLC. licenses this file to you under the Apache License, // Version 2.0 (the "License"); you may not use this file except diff --git a/compiler-plugin-tests/src/test/resources/static_code_analyzer/ballerina_packages/rule2/argon_func_var_pos_arg.bal b/compiler-plugin-tests/src/test/resources/static_code_analyzer/ballerina_packages/rule2/argon_func_var_pos_arg.bal index ad3ba9fc..5d8ff4d4 100644 --- a/compiler-plugin-tests/src/test/resources/static_code_analyzer/ballerina_packages/rule2/argon_func_var_pos_arg.bal +++ b/compiler-plugin-tests/src/test/resources/static_code_analyzer/ballerina_packages/rule2/argon_func_var_pos_arg.bal @@ -1,4 +1,4 @@ -// Copyright (c) 2025 WSO2 LLC. (http://www.wso2.org) +// Copyright (c) 2025 WSO2 LLC. (http://www.wso2.com) // // WSO2 LLC. licenses this file to you under the Apache License, // Version 2.0 (the "License"); you may not use this file except diff --git a/compiler-plugin-tests/src/test/resources/static_code_analyzer/ballerina_packages/rule2/argon_inline_named_arg.bal b/compiler-plugin-tests/src/test/resources/static_code_analyzer/ballerina_packages/rule2/argon_inline_named_arg.bal index 5a4747f9..00772606 100644 --- a/compiler-plugin-tests/src/test/resources/static_code_analyzer/ballerina_packages/rule2/argon_inline_named_arg.bal +++ b/compiler-plugin-tests/src/test/resources/static_code_analyzer/ballerina_packages/rule2/argon_inline_named_arg.bal @@ -1,4 +1,4 @@ -// Copyright (c) 2025 WSO2 LLC. (http://www.wso2.org) +// Copyright (c) 2025 WSO2 LLC. (http://www.wso2.com) // // WSO2 LLC. licenses this file to you under the Apache License, // Version 2.0 (the "License"); you may not use this file except diff --git a/compiler-plugin-tests/src/test/resources/static_code_analyzer/ballerina_packages/rule2/argon_inline_pos_arg.bal b/compiler-plugin-tests/src/test/resources/static_code_analyzer/ballerina_packages/rule2/argon_inline_pos_arg.bal index 72f14384..0c48ea4b 100644 --- a/compiler-plugin-tests/src/test/resources/static_code_analyzer/ballerina_packages/rule2/argon_inline_pos_arg.bal +++ b/compiler-plugin-tests/src/test/resources/static_code_analyzer/ballerina_packages/rule2/argon_inline_pos_arg.bal @@ -1,4 +1,4 @@ -// Copyright (c) 2025 WSO2 LLC. (http://www.wso2.org) +// Copyright (c) 2025 WSO2 LLC. (http://www.wso2.com) // // WSO2 LLC. licenses this file to you under the Apache License, // Version 2.0 (the "License"); you may not use this file except diff --git a/compiler-plugin-tests/src/test/resources/static_code_analyzer/ballerina_packages/rule2/argon_mod_var_named_arg.bal b/compiler-plugin-tests/src/test/resources/static_code_analyzer/ballerina_packages/rule2/argon_mod_var_named_arg.bal index 47e21059..81559feb 100644 --- a/compiler-plugin-tests/src/test/resources/static_code_analyzer/ballerina_packages/rule2/argon_mod_var_named_arg.bal +++ b/compiler-plugin-tests/src/test/resources/static_code_analyzer/ballerina_packages/rule2/argon_mod_var_named_arg.bal @@ -1,4 +1,4 @@ -// Copyright (c) 2025 WSO2 LLC. (http://www.wso2.org) +// Copyright (c) 2025 WSO2 LLC. (http://www.wso2.com) // // WSO2 LLC. licenses this file to you under the Apache License, // Version 2.0 (the "License"); you may not use this file except diff --git a/compiler-plugin-tests/src/test/resources/static_code_analyzer/ballerina_packages/rule2/argon_mod_var_pos_arg.bal b/compiler-plugin-tests/src/test/resources/static_code_analyzer/ballerina_packages/rule2/argon_mod_var_pos_arg.bal index c5b817c3..46ce9fbd 100644 --- a/compiler-plugin-tests/src/test/resources/static_code_analyzer/ballerina_packages/rule2/argon_mod_var_pos_arg.bal +++ b/compiler-plugin-tests/src/test/resources/static_code_analyzer/ballerina_packages/rule2/argon_mod_var_pos_arg.bal @@ -1,4 +1,4 @@ -// Copyright (c) 2025 WSO2 LLC. (http://www.wso2.org) +// Copyright (c) 2025 WSO2 LLC. (http://www.wso2.com) // // WSO2 LLC. licenses this file to you under the Apache License, // Version 2.0 (the "License"); you may not use this file except diff --git a/compiler-plugin-tests/src/test/resources/static_code_analyzer/ballerina_packages/rule2/bcrypt_func_var_named_arg.bal b/compiler-plugin-tests/src/test/resources/static_code_analyzer/ballerina_packages/rule2/bcrypt_func_var_named_arg.bal index aab4d280..5f45e77f 100644 --- a/compiler-plugin-tests/src/test/resources/static_code_analyzer/ballerina_packages/rule2/bcrypt_func_var_named_arg.bal +++ b/compiler-plugin-tests/src/test/resources/static_code_analyzer/ballerina_packages/rule2/bcrypt_func_var_named_arg.bal @@ -1,4 +1,4 @@ -// Copyright (c) 2025 WSO2 LLC. (http://www.wso2.org) +// Copyright (c) 2025 WSO2 LLC. (http://www.wso2.com) // // WSO2 LLC. licenses this file to you under the Apache License, // Version 2.0 (the "License"); you may not use this file except diff --git a/compiler-plugin-tests/src/test/resources/static_code_analyzer/ballerina_packages/rule2/bcrypt_func_var_pos_arg.bal b/compiler-plugin-tests/src/test/resources/static_code_analyzer/ballerina_packages/rule2/bcrypt_func_var_pos_arg.bal index e1200b9f..7801dd4a 100644 --- a/compiler-plugin-tests/src/test/resources/static_code_analyzer/ballerina_packages/rule2/bcrypt_func_var_pos_arg.bal +++ b/compiler-plugin-tests/src/test/resources/static_code_analyzer/ballerina_packages/rule2/bcrypt_func_var_pos_arg.bal @@ -1,4 +1,4 @@ -// Copyright (c) 2025 WSO2 LLC. (http://www.wso2.org) +// Copyright (c) 2025 WSO2 LLC. (http://www.wso2.com) // // WSO2 LLC. licenses this file to you under the Apache License, // Version 2.0 (the "License"); you may not use this file except diff --git a/compiler-plugin-tests/src/test/resources/static_code_analyzer/ballerina_packages/rule2/bcrypt_inline_named_arg.bal b/compiler-plugin-tests/src/test/resources/static_code_analyzer/ballerina_packages/rule2/bcrypt_inline_named_arg.bal index 09d09604..cd08eb98 100644 --- a/compiler-plugin-tests/src/test/resources/static_code_analyzer/ballerina_packages/rule2/bcrypt_inline_named_arg.bal +++ b/compiler-plugin-tests/src/test/resources/static_code_analyzer/ballerina_packages/rule2/bcrypt_inline_named_arg.bal @@ -1,4 +1,4 @@ -// Copyright (c) 2025 WSO2 LLC. (http://www.wso2.org) +// Copyright (c) 2025 WSO2 LLC. (http://www.wso2.com) // // WSO2 LLC. licenses this file to you under the Apache License, // Version 2.0 (the "License"); you may not use this file except diff --git a/compiler-plugin-tests/src/test/resources/static_code_analyzer/ballerina_packages/rule2/bcrypt_inline_pos_arg.bal b/compiler-plugin-tests/src/test/resources/static_code_analyzer/ballerina_packages/rule2/bcrypt_inline_pos_arg.bal index 10bfcb9a..48bcfcca 100644 --- a/compiler-plugin-tests/src/test/resources/static_code_analyzer/ballerina_packages/rule2/bcrypt_inline_pos_arg.bal +++ b/compiler-plugin-tests/src/test/resources/static_code_analyzer/ballerina_packages/rule2/bcrypt_inline_pos_arg.bal @@ -1,4 +1,4 @@ -// Copyright (c) 2025 WSO2 LLC. (http://www.wso2.org) +// Copyright (c) 2025 WSO2 LLC. (http://www.wso2.com) // // WSO2 LLC. licenses this file to you under the Apache License, // Version 2.0 (the "License"); you may not use this file except diff --git a/compiler-plugin-tests/src/test/resources/static_code_analyzer/ballerina_packages/rule2/bcrypt_mod_var_named_arg.bal b/compiler-plugin-tests/src/test/resources/static_code_analyzer/ballerina_packages/rule2/bcrypt_mod_var_named_arg.bal index f272b4ab..65b39420 100644 --- a/compiler-plugin-tests/src/test/resources/static_code_analyzer/ballerina_packages/rule2/bcrypt_mod_var_named_arg.bal +++ b/compiler-plugin-tests/src/test/resources/static_code_analyzer/ballerina_packages/rule2/bcrypt_mod_var_named_arg.bal @@ -1,4 +1,4 @@ -// Copyright (c) 2025 WSO2 LLC. (http://www.wso2.org) +// Copyright (c) 2025 WSO2 LLC. (http://www.wso2.com) // // WSO2 LLC. licenses this file to you under the Apache License, // Version 2.0 (the "License"); you may not use this file except diff --git a/compiler-plugin-tests/src/test/resources/static_code_analyzer/ballerina_packages/rule2/bcrypt_mod_var_pos_arg.bal b/compiler-plugin-tests/src/test/resources/static_code_analyzer/ballerina_packages/rule2/bcrypt_mod_var_pos_arg.bal index 5f194182..b7658b4c 100644 --- a/compiler-plugin-tests/src/test/resources/static_code_analyzer/ballerina_packages/rule2/bcrypt_mod_var_pos_arg.bal +++ b/compiler-plugin-tests/src/test/resources/static_code_analyzer/ballerina_packages/rule2/bcrypt_mod_var_pos_arg.bal @@ -1,4 +1,4 @@ -// Copyright (c) 2025 WSO2 LLC. (http://www.wso2.org) +// Copyright (c) 2025 WSO2 LLC. (http://www.wso2.com) // // WSO2 LLC. licenses this file to you under the Apache License, // Version 2.0 (the "License"); you may not use this file except diff --git a/compiler-plugin-tests/src/test/resources/static_code_analyzer/ballerina_packages/rule2/modules/module/module.bal b/compiler-plugin-tests/src/test/resources/static_code_analyzer/ballerina_packages/rule2/modules/module/module.bal new file mode 100644 index 00000000..aaf60623 --- /dev/null +++ b/compiler-plugin-tests/src/test/resources/static_code_analyzer/ballerina_packages/rule2/modules/module/module.bal @@ -0,0 +1,20 @@ +// Copyright (c) 2025 WSO2 LLC. (http://www.wso2.com) +// +// WSO2 LLC. licenses this file to you under the Apache License, +// Version 2.0 (the "License"); you may not use this file except +// in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +public const MEMORY = 16384; +public const MINIMUM_ALLOWED_MEMORY = 19456; + +public int memory = 16384; diff --git a/compiler-plugin-tests/src/test/resources/static_code_analyzer/ballerina_packages/rule2/pbkdf2_func.bal b/compiler-plugin-tests/src/test/resources/static_code_analyzer/ballerina_packages/rule2/pbkdf2_func.bal new file mode 100644 index 00000000..cf8aa9ac --- /dev/null +++ b/compiler-plugin-tests/src/test/resources/static_code_analyzer/ballerina_packages/rule2/pbkdf2_func.bal @@ -0,0 +1,42 @@ +// Copyright (c) 2025 WSO2 LLC. (http://www.wso2.com) +// +// WSO2 LLC. licenses this file to you under the Apache License, +// Version 2.0 (the "License"); you may not use this file except +// in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +import ballerina/crypto; + +public function pbkdf2Func() returns error? { + // Default parameters - iterations: 10000 which is lower than recommended for all algorithms + // Hence vulnerable + _ = check crypto:hashPbkdf2("password"); // Default algorithm is SHA-256. + _ = check crypto:hashPbkdf2("password", algorithm = crypto:SHA1); + _ = check crypto:hashPbkdf2("password", algorithm = crypto:SHA256); + _ = check crypto:hashPbkdf2("password", algorithm = crypto:SHA512); + + + // Default algorithm is SHA-256. Iterations set to minimum recommended value for SHA-256 (600000) + _ = check crypto:hashPbkdf2("password", 600000); + _ = check crypto:hashPbkdf2("password", 600000, "SHA256"); + + // Iterations set to minimum recommended value for SHA-1 (1000000) + _ = check crypto:hashPbkdf2("password", 1300000, crypto:SHA1); + + // Iterations set to minimum recommended value for SHA-512 (500000) + _ = check crypto:hashPbkdf2("password", 210000, "SHA512"); + + // Vulnerable examples + _ = check crypto:hashPbkdf2("password", 200000, crypto:SHA256); + _ = check crypto:hashPbkdf2("password", 900000, "SHA1"); + _ = check crypto:hashPbkdf2("password", 200000, crypto:SHA512); +} diff --git a/compiler-plugin-tests/src/test/resources/static_code_analyzer/ballerina_packages/rule2/pbkdf2_func_var_named_arg.bal b/compiler-plugin-tests/src/test/resources/static_code_analyzer/ballerina_packages/rule2/pbkdf2_func_var_named_arg.bal index 2fe4c74b..fff7c8d8 100644 --- a/compiler-plugin-tests/src/test/resources/static_code_analyzer/ballerina_packages/rule2/pbkdf2_func_var_named_arg.bal +++ b/compiler-plugin-tests/src/test/resources/static_code_analyzer/ballerina_packages/rule2/pbkdf2_func_var_named_arg.bal @@ -1,4 +1,4 @@ -// Copyright (c) 2025 WSO2 LLC. (http://www.wso2.org) +// Copyright (c) 2025 WSO2 LLC. (http://www.wso2.com) // // WSO2 LLC. licenses this file to you under the Apache License, // Version 2.0 (the "License"); you may not use this file except diff --git a/compiler-plugin-tests/src/test/resources/static_code_analyzer/ballerina_packages/rule2/pbkdf2_func_var_pos_arg.bal b/compiler-plugin-tests/src/test/resources/static_code_analyzer/ballerina_packages/rule2/pbkdf2_func_var_pos_arg.bal index 82a75dba..641f973f 100644 --- a/compiler-plugin-tests/src/test/resources/static_code_analyzer/ballerina_packages/rule2/pbkdf2_func_var_pos_arg.bal +++ b/compiler-plugin-tests/src/test/resources/static_code_analyzer/ballerina_packages/rule2/pbkdf2_func_var_pos_arg.bal @@ -1,4 +1,4 @@ -// Copyright (c) 2025 WSO2 LLC. (http://www.wso2.org) +// Copyright (c) 2025 WSO2 LLC. (http://www.wso2.com) // // WSO2 LLC. licenses this file to you under the Apache License, // Version 2.0 (the "License"); you may not use this file except diff --git a/compiler-plugin-tests/src/test/resources/static_code_analyzer/ballerina_packages/rule2/pbkdf2_inline_named_arg.bal b/compiler-plugin-tests/src/test/resources/static_code_analyzer/ballerina_packages/rule2/pbkdf2_inline_named_arg.bal index ea8186a8..c6fc40e9 100644 --- a/compiler-plugin-tests/src/test/resources/static_code_analyzer/ballerina_packages/rule2/pbkdf2_inline_named_arg.bal +++ b/compiler-plugin-tests/src/test/resources/static_code_analyzer/ballerina_packages/rule2/pbkdf2_inline_named_arg.bal @@ -1,4 +1,4 @@ -// Copyright (c) 2025 WSO2 LLC. (http://www.wso2.org) +// Copyright (c) 2025 WSO2 LLC. (http://www.wso2.com) // // WSO2 LLC. licenses this file to you under the Apache License, // Version 2.0 (the "License"); you may not use this file except diff --git a/compiler-plugin-tests/src/test/resources/static_code_analyzer/ballerina_packages/rule2/pbkdf2_inline_pos_arg.bal b/compiler-plugin-tests/src/test/resources/static_code_analyzer/ballerina_packages/rule2/pbkdf2_inline_pos_arg.bal index 904c9d91..d3729d07 100644 --- a/compiler-plugin-tests/src/test/resources/static_code_analyzer/ballerina_packages/rule2/pbkdf2_inline_pos_arg.bal +++ b/compiler-plugin-tests/src/test/resources/static_code_analyzer/ballerina_packages/rule2/pbkdf2_inline_pos_arg.bal @@ -1,4 +1,4 @@ -// Copyright (c) 2025 WSO2 LLC. (http://www.wso2.org) +// Copyright (c) 2025 WSO2 LLC. (http://www.wso2.com) // // WSO2 LLC. licenses this file to you under the Apache License, // Version 2.0 (the "License"); you may not use this file except diff --git a/compiler-plugin-tests/src/test/resources/static_code_analyzer/ballerina_packages/rule2/pbkdf2_mod_var_named_arg.bal b/compiler-plugin-tests/src/test/resources/static_code_analyzer/ballerina_packages/rule2/pbkdf2_mod_var_named_arg.bal index 16d9937a..c5c64f4a 100644 --- a/compiler-plugin-tests/src/test/resources/static_code_analyzer/ballerina_packages/rule2/pbkdf2_mod_var_named_arg.bal +++ b/compiler-plugin-tests/src/test/resources/static_code_analyzer/ballerina_packages/rule2/pbkdf2_mod_var_named_arg.bal @@ -1,4 +1,4 @@ -// Copyright (c) 2025 WSO2 LLC. (http://www.wso2.org) +// Copyright (c) 2025 WSO2 LLC. (http://www.wso2.com) // // WSO2 LLC. licenses this file to you under the Apache License, // Version 2.0 (the "License"); you may not use this file except diff --git a/compiler-plugin-tests/src/test/resources/static_code_analyzer/ballerina_packages/rule2/pbkdf2_mod_var_pos_arg.bal b/compiler-plugin-tests/src/test/resources/static_code_analyzer/ballerina_packages/rule2/pbkdf2_mod_var_pos_arg.bal index add43235..ab01d983 100644 --- a/compiler-plugin-tests/src/test/resources/static_code_analyzer/ballerina_packages/rule2/pbkdf2_mod_var_pos_arg.bal +++ b/compiler-plugin-tests/src/test/resources/static_code_analyzer/ballerina_packages/rule2/pbkdf2_mod_var_pos_arg.bal @@ -1,4 +1,4 @@ -// Copyright (c) 2025 WSO2 LLC. (http://www.wso2.org) +// Copyright (c) 2025 WSO2 LLC. (http://www.wso2.com) // // WSO2 LLC. licenses this file to you under the Apache License, // Version 2.0 (the "License"); you may not use this file except diff --git a/compiler-plugin-tests/src/test/resources/static_code_analyzer/ballerina_packages/rule3/func_hardcoded_iv_param.bal b/compiler-plugin-tests/src/test/resources/static_code_analyzer/ballerina_packages/rule3/func_hardcoded_iv_param.bal new file mode 100644 index 00000000..c8493093 --- /dev/null +++ b/compiler-plugin-tests/src/test/resources/static_code_analyzer/ballerina_packages/rule3/func_hardcoded_iv_param.bal @@ -0,0 +1,61 @@ +// Copyright (c) 2025 WSO2 LLC. (http://www.wso2.com) +// +// WSO2 LLC. licenses this file to you under the Apache License, +// Version 2.0 (the "License"); you may not use this file except +// in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +import ballerina/crypto; +import wso2/rule3.'module as mod; + +const HARDCODED_IV = "constHardcodedIV!"; + +public function funcHardcodedIV(string data) returns error? { + byte[16] key = [16, 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1]; + byte[] dataBytes = data.toBytes(); + _ = check crypto:encryptAesGcm(dataBytes, key, [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16]); + + _ = check crypto:encryptAesGcm(dataBytes, key, "hardcodedIV1234".toBytes()); + byte[] iv; + iv = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16]; + _ = check crypto:encryptAesGcm(dataBytes, key, iv); + + iv = "anotherHardcodedIV".toBytes(); + _ = check crypto:encryptAesGcm(dataBytes, key, iv); + + string ivStr = "dynamicIVValue"; + iv = ivStr.toBytes(); + _ = check crypto:encryptAesGcm(dataBytes, key, iv); + + iv = HARDCODED_IV.toBytes(); + _ = check crypto:encryptAesGcm(dataBytes, key, iv); + do { + iv = mod:IV_STRING_VALUE.toBytes(); + _ = check crypto:encryptAesGcm(dataBytes, key, iv); + } +} + +public function funcHardcodedIVNegative(string data) returns error? { + byte[16] key = [16, 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1]; + byte[] dataBytes = data.toBytes(); + // Negative test where we cannot determine at compile time + byte[] iv = mod:ivStringValue.toBytes(); + _ = check crypto:encryptAesGcm(dataBytes, key, iv); + + // Negative test with conditional assignment which is not supported yet + if data.length() > 5 { + iv = "shortIV".toBytes(); + } else { + iv = "longerHardcodedIV".toBytes(); + } + _ = check crypto:encryptAesGcm(dataBytes, key, iv); +} diff --git a/compiler-plugin-tests/src/test/resources/static_code_analyzer/ballerina_packages/rule3/func_var_named_arg.bal b/compiler-plugin-tests/src/test/resources/static_code_analyzer/ballerina_packages/rule3/func_var_named_arg.bal index 28546ed2..5085746e 100644 --- a/compiler-plugin-tests/src/test/resources/static_code_analyzer/ballerina_packages/rule3/func_var_named_arg.bal +++ b/compiler-plugin-tests/src/test/resources/static_code_analyzer/ballerina_packages/rule3/func_var_named_arg.bal @@ -1,4 +1,4 @@ -// Copyright (c) 2025 WSO2 LLC. (http://www.wso2.org) +// Copyright (c) 2025 WSO2 LLC. (http://www.wso2.com) // // WSO2 LLC. licenses this file to you under the Apache License, // Version 2.0 (the "License"); you may not use this file except diff --git a/compiler-plugin-tests/src/test/resources/static_code_analyzer/ballerina_packages/rule3/func_var_pos_arg.bal b/compiler-plugin-tests/src/test/resources/static_code_analyzer/ballerina_packages/rule3/func_var_pos_arg.bal index 07f640c7..67897b63 100644 --- a/compiler-plugin-tests/src/test/resources/static_code_analyzer/ballerina_packages/rule3/func_var_pos_arg.bal +++ b/compiler-plugin-tests/src/test/resources/static_code_analyzer/ballerina_packages/rule3/func_var_pos_arg.bal @@ -1,4 +1,4 @@ -// Copyright (c) 2025 WSO2 LLC. (http://www.wso2.org) +// Copyright (c) 2025 WSO2 LLC. (http://www.wso2.com) // // WSO2 LLC. licenses this file to you under the Apache License, // Version 2.0 (the "License"); you may not use this file except diff --git a/compiler-plugin-tests/src/test/resources/static_code_analyzer/ballerina_packages/rule3/inline_named_arg.bal b/compiler-plugin-tests/src/test/resources/static_code_analyzer/ballerina_packages/rule3/inline_named_arg.bal index f24affd0..26ae881b 100644 --- a/compiler-plugin-tests/src/test/resources/static_code_analyzer/ballerina_packages/rule3/inline_named_arg.bal +++ b/compiler-plugin-tests/src/test/resources/static_code_analyzer/ballerina_packages/rule3/inline_named_arg.bal @@ -1,4 +1,4 @@ -// Copyright (c) 2025 WSO2 LLC. (http://www.wso2.org) +// Copyright (c) 2025 WSO2 LLC. (http://www.wso2.com) // // WSO2 LLC. licenses this file to you under the Apache License, // Version 2.0 (the "License"); you may not use this file except diff --git a/compiler-plugin-tests/src/test/resources/static_code_analyzer/ballerina_packages/rule3/inline_pos_arg.bal b/compiler-plugin-tests/src/test/resources/static_code_analyzer/ballerina_packages/rule3/inline_pos_arg.bal index d534b33e..ea7c66cc 100644 --- a/compiler-plugin-tests/src/test/resources/static_code_analyzer/ballerina_packages/rule3/inline_pos_arg.bal +++ b/compiler-plugin-tests/src/test/resources/static_code_analyzer/ballerina_packages/rule3/inline_pos_arg.bal @@ -1,4 +1,4 @@ -// Copyright (c) 2025 WSO2 LLC. (http://www.wso2.org) +// Copyright (c) 2025 WSO2 LLC. (http://www.wso2.com) // // WSO2 LLC. licenses this file to you under the Apache License, // Version 2.0 (the "License"); you may not use this file except diff --git a/compiler-plugin-tests/src/test/resources/static_code_analyzer/ballerina_packages/rule3/mod_var_named_arg.bal b/compiler-plugin-tests/src/test/resources/static_code_analyzer/ballerina_packages/rule3/mod_var_named_arg.bal index b5851f4e..fc770977 100644 --- a/compiler-plugin-tests/src/test/resources/static_code_analyzer/ballerina_packages/rule3/mod_var_named_arg.bal +++ b/compiler-plugin-tests/src/test/resources/static_code_analyzer/ballerina_packages/rule3/mod_var_named_arg.bal @@ -1,4 +1,4 @@ -// Copyright (c) 2025 WSO2 LLC. (http://www.wso2.org) +// Copyright (c) 2025 WSO2 LLC. (http://www.wso2.com) // // WSO2 LLC. licenses this file to you under the Apache License, // Version 2.0 (the "License"); you may not use this file except diff --git a/compiler-plugin-tests/src/test/resources/static_code_analyzer/ballerina_packages/rule3/mod_var_pos_arg.bal b/compiler-plugin-tests/src/test/resources/static_code_analyzer/ballerina_packages/rule3/mod_var_pos_arg.bal index 435a57a7..cdc7db2f 100644 --- a/compiler-plugin-tests/src/test/resources/static_code_analyzer/ballerina_packages/rule3/mod_var_pos_arg.bal +++ b/compiler-plugin-tests/src/test/resources/static_code_analyzer/ballerina_packages/rule3/mod_var_pos_arg.bal @@ -1,4 +1,4 @@ -// Copyright (c) 2025 WSO2 LLC. (http://www.wso2.org) +// Copyright (c) 2025 WSO2 LLC. (http://www.wso2.com) // // WSO2 LLC. licenses this file to you under the Apache License, // Version 2.0 (the "License"); you may not use this file except diff --git a/compiler-plugin-tests/src/test/resources/static_code_analyzer/ballerina_packages/rule3/modules/module/module.bal b/compiler-plugin-tests/src/test/resources/static_code_analyzer/ballerina_packages/rule3/modules/module/module.bal new file mode 100644 index 00000000..354f599c --- /dev/null +++ b/compiler-plugin-tests/src/test/resources/static_code_analyzer/ballerina_packages/rule3/modules/module/module.bal @@ -0,0 +1,19 @@ +// Copyright (c) 2025 WSO2 LLC. (http://www.wso2.com) +// +// WSO2 LLC. licenses this file to you under the Apache License, +// Version 2.0 (the "License"); you may not use this file except +// in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +public const IV_STRING_VALUE = "IV_CONST_VALUE"; + +public string ivStringValue = "IV_VALUE"; diff --git a/compiler-plugin-tests/src/test/resources/static_code_analyzer/expected_output/rule1.json b/compiler-plugin-tests/src/test/resources/static_code_analyzer/expected_output/rule1.json index ef8ede4a..b50cc84c 100644 --- a/compiler-plugin-tests/src/test/resources/static_code_analyzer/expected_output/rule1.json +++ b/compiler-plugin-tests/src/test/resources/static_code_analyzer/expected_output/rule1.json @@ -99,26 +99,6 @@ "fileName": "rule1/aes_cbc.bal", "filePath": "module-ballerina-crypto/compiler-plugin-tests/src/test/resources/static_code_analyzer/ballerina_packages/rule1/aes_cbc.bal" }, - { - "location": { - "filePath": "aes_cbc.bal", - "startLine": 30, - "endLine": 30, - "startColumn": 21, - "endColumn": 67, - "startOffset": 1203, - "length": 46 - }, - "rule": { - "id": "ballerina/crypto:4", - "numericId": 4, - "description": "Secure random number generators should not output predictable values", - "ruleKind": "VULNERABILITY" - }, - "source": "BUILT_IN", - "fileName": "rule1/aes_cbc.bal", - "filePath": "module-ballerina-crypto/compiler-plugin-tests/src/test/resources/static_code_analyzer/ballerina_packages/rule1/aes_cbc.bal" - }, { "location": { "filePath": "aes_cbc_as_import.bal", @@ -139,26 +119,6 @@ "fileName": "rule1/aes_cbc_as_import.bal", "filePath": "module-ballerina-crypto/compiler-plugin-tests/src/test/resources/static_code_analyzer/ballerina_packages/rule1/aes_cbc_as_import.bal" }, - { - "location": { - "filePath": "aes_cbc_as_import.bal", - "startLine": 30, - "endLine": 30, - "startColumn": 21, - "endColumn": 62, - "startOffset": 1216, - "length": 41 - }, - "rule": { - "id": "ballerina/crypto:4", - "numericId": 4, - "description": "Secure random number generators should not output predictable values", - "ruleKind": "VULNERABILITY" - }, - "source": "BUILT_IN", - "fileName": "rule1/aes_cbc_as_import.bal", - "filePath": "module-ballerina-crypto/compiler-plugin-tests/src/test/resources/static_code_analyzer/ballerina_packages/rule1/aes_cbc_as_import.bal" - }, { "location": { "filePath": "aes_ecb.bal", @@ -198,5 +158,125 @@ "source": "BUILT_IN", "fileName": "rule1/aes_ecb_as_import.bal", "filePath": "module-ballerina-crypto/compiler-plugin-tests/src/test/resources/static_code_analyzer/ballerina_packages/rule1/aes_ecb_as_import.bal" + }, + { + "location": { + "filePath": "weak_cipher_algo.bal", + "startLine": 23, + "endLine": 23, + "startColumn": 14, + "endColumn": 56, + "startOffset": 854, + "length": 42 + }, + "rule": { + "id": "ballerina/crypto:1", + "numericId": 1, + "description": "Avoid using insecure cipher modes or padding schemes", + "ruleKind": "VULNERABILITY" + }, + "source": "BUILT_IN", + "fileName": "rule1/weak_cipher_algo.bal", + "filePath": "module-ballerina-crypto/compiler-plugin-tests/src/test/resources/static_code_analyzer/ballerina_packages/rule1/weak_cipher_algo.bal" + }, + { + "location": { + "filePath": "weak_cipher_algo.bal", + "startLine": 27, + "endLine": 27, + "startColumn": 14, + "endColumn": 60, + "startOffset": 1003, + "length": 46 + }, + "rule": { + "id": "ballerina/crypto:1", + "numericId": 1, + "description": "Avoid using insecure cipher modes or padding schemes", + "ruleKind": "VULNERABILITY" + }, + "source": "BUILT_IN", + "fileName": "rule1/weak_cipher_algo.bal", + "filePath": "module-ballerina-crypto/compiler-plugin-tests/src/test/resources/static_code_analyzer/ballerina_packages/rule1/weak_cipher_algo.bal" + }, + { + "location": { + "filePath": "weak_cipher_algo.bal", + "startLine": 27, + "endLine": 27, + "startColumn": 14, + "endColumn": 60, + "startOffset": 1003, + "length": 46 + }, + "rule": { + "id": "ballerina/crypto:3", + "numericId": 3, + "description": "Avoid reusing counter mode initialization vectors", + "ruleKind": "VULNERABILITY" + }, + "source": "BUILT_IN", + "fileName": "rule1/weak_cipher_algo.bal", + "filePath": "module-ballerina-crypto/compiler-plugin-tests/src/test/resources/static_code_analyzer/ballerina_packages/rule1/weak_cipher_algo.bal" + }, + { + "location": { + "filePath": "weak_cipher_algo.bal", + "startLine": 35, + "endLine": 35, + "startColumn": 14, + "endColumn": 71, + "startOffset": 1346, + "length": 57 + }, + "rule": { + "id": "ballerina/crypto:1", + "numericId": 1, + "description": "Avoid using insecure cipher modes or padding schemes", + "ruleKind": "VULNERABILITY" + }, + "source": "BUILT_IN", + "fileName": "rule1/weak_cipher_algo.bal", + "filePath": "module-ballerina-crypto/compiler-plugin-tests/src/test/resources/static_code_analyzer/ballerina_packages/rule1/weak_cipher_algo.bal" + }, + { + "location": { + "filePath": "weak_cipher_algo.bal", + "startLine": 36, + "endLine": 36, + "startColumn": 14, + "endColumn": 66, + "startOffset": 1419, + "length": 52 + }, + "rule": { + "id": "ballerina/crypto:1", + "numericId": 1, + "description": "Avoid using insecure cipher modes or padding schemes", + "ruleKind": "VULNERABILITY" + }, + "source": "BUILT_IN", + "fileName": "rule1/weak_cipher_algo.bal", + "filePath": "module-ballerina-crypto/compiler-plugin-tests/src/test/resources/static_code_analyzer/ballerina_packages/rule1/weak_cipher_algo.bal" + }, + { + "location": { + "filePath": "weak_cipher_algo.bal", + "startLine": 39, + "endLine": 39, + "startColumn": 14, + "endColumn": 57, + "startOffset": 1566, + "length": 43 + }, + "rule": { + "id": "ballerina/crypto:1", + "numericId": 1, + "description": "Avoid using insecure cipher modes or padding schemes", + "ruleKind": "VULNERABILITY" + }, + "source": "BUILT_IN", + "fileName": "rule1/weak_cipher_algo.bal", + "filePath": "module-ballerina-crypto/compiler-plugin-tests/src/test/resources/static_code_analyzer/ballerina_packages/rule1/weak_cipher_algo.bal" } ] diff --git a/compiler-plugin-tests/src/test/resources/static_code_analyzer/expected_output/rule2.json b/compiler-plugin-tests/src/test/resources/static_code_analyzer/expected_output/rule2.json index 8c37a13a..cb9c9eb4 100644 --- a/compiler-plugin-tests/src/test/resources/static_code_analyzer/expected_output/rule2.json +++ b/compiler-plugin-tests/src/test/resources/static_code_analyzer/expected_output/rule2.json @@ -1,4 +1,24 @@ [ + { + "location": { + "filePath": "argon_func.bal", + "startLine": 21, + "endLine": 21, + "startColumn": 0, + "endColumn": 6, + "startOffset": 726, + "length": 6 + }, + "rule": { + "id": "ballerina:3", + "numericId": 3, + "description": "Non isolated public function", + "ruleKind": "CODE_SMELL" + }, + "source": "BUILT_IN", + "fileName": "rule2/argon_func.bal", + "filePath": "module-ballerina-crypto/compiler-plugin-tests/src/test/resources/static_code_analyzer/ballerina_packages/rule2/argon_func.bal" + }, { "location": { "filePath": "argon_func_var_named_arg.bal", @@ -239,6 +259,26 @@ "fileName": "rule2/bcrypt_mod_var_pos_arg.bal", "filePath": "module-ballerina-crypto/compiler-plugin-tests/src/test/resources/static_code_analyzer/ballerina_packages/rule2/bcrypt_mod_var_pos_arg.bal" }, + { + "location": { + "filePath": "pbkdf2_func.bal", + "startLine": 18, + "endLine": 18, + "startColumn": 0, + "endColumn": 6, + "startOffset": 669, + "length": 6 + }, + "rule": { + "id": "ballerina:3", + "numericId": 3, + "description": "Non isolated public function", + "ruleKind": "CODE_SMELL" + }, + "source": "BUILT_IN", + "fileName": "rule2/pbkdf2_func.bal", + "filePath": "module-ballerina-crypto/compiler-plugin-tests/src/test/resources/static_code_analyzer/ballerina_packages/rule2/pbkdf2_func.bal" + }, { "location": { "filePath": "pbkdf2_func_var_named_arg.bal", @@ -359,6 +399,86 @@ "fileName": "rule2/pbkdf2_mod_var_pos_arg.bal", "filePath": "module-ballerina-crypto/compiler-plugin-tests/src/test/resources/static_code_analyzer/ballerina_packages/rule2/pbkdf2_mod_var_pos_arg.bal" }, + { + "location": { + "filePath": "argon_func.bal", + "startLine": 31, + "endLine": 31, + "startColumn": 14, + "endColumn": 46, + "startOffset": 1238, + "length": 32 + }, + "rule": { + "id": "ballerina/crypto:2", + "numericId": 2, + "description": "Avoid using fast hashing algorithms", + "ruleKind": "VULNERABILITY" + }, + "source": "BUILT_IN", + "fileName": "rule2/argon_func.bal", + "filePath": "module-ballerina-crypto/compiler-plugin-tests/src/test/resources/static_code_analyzer/ballerina_packages/rule2/argon_func.bal" + }, + { + "location": { + "filePath": "argon_func.bal", + "startLine": 33, + "endLine": 33, + "startColumn": 14, + "endColumn": 58, + "startOffset": 1300, + "length": 44 + }, + "rule": { + "id": "ballerina/crypto:2", + "numericId": 2, + "description": "Avoid using fast hashing algorithms", + "ruleKind": "VULNERABILITY" + }, + "source": "BUILT_IN", + "fileName": "rule2/argon_func.bal", + "filePath": "module-ballerina-crypto/compiler-plugin-tests/src/test/resources/static_code_analyzer/ballerina_packages/rule2/argon_func.bal" + }, + { + "location": { + "filePath": "argon_func.bal", + "startLine": 35, + "endLine": 35, + "startColumn": 14, + "endColumn": 60, + "startOffset": 1388, + "length": 46 + }, + "rule": { + "id": "ballerina/crypto:2", + "numericId": 2, + "description": "Avoid using fast hashing algorithms", + "ruleKind": "VULNERABILITY" + }, + "source": "BUILT_IN", + "fileName": "rule2/argon_func.bal", + "filePath": "module-ballerina-crypto/compiler-plugin-tests/src/test/resources/static_code_analyzer/ballerina_packages/rule2/argon_func.bal" + }, + { + "location": { + "filePath": "argon_func.bal", + "startLine": 37, + "endLine": 37, + "startColumn": 14, + "endColumn": 64, + "startOffset": 1485, + "length": 50 + }, + "rule": { + "id": "ballerina/crypto:2", + "numericId": 2, + "description": "Avoid using fast hashing algorithms", + "ruleKind": "VULNERABILITY" + }, + "source": "BUILT_IN", + "fileName": "rule2/argon_func.bal", + "filePath": "module-ballerina-crypto/compiler-plugin-tests/src/test/resources/static_code_analyzer/ballerina_packages/rule2/argon_func.bal" + }, { "location": { "filePath": "argon_func_var_named_arg.bal", @@ -599,6 +719,146 @@ "fileName": "rule2/bcrypt_mod_var_pos_arg.bal", "filePath": "module-ballerina-crypto/compiler-plugin-tests/src/test/resources/static_code_analyzer/ballerina_packages/rule2/bcrypt_mod_var_pos_arg.bal" }, + { + "location": { + "filePath": "pbkdf2_func.bal", + "startLine": 21, + "endLine": 21, + "startColumn": 14, + "endColumn": 43, + "startOffset": 850, + "length": 29 + }, + "rule": { + "id": "ballerina/crypto:2", + "numericId": 2, + "description": "Avoid using fast hashing algorithms", + "ruleKind": "VULNERABILITY" + }, + "source": "BUILT_IN", + "fileName": "rule2/pbkdf2_func.bal", + "filePath": "module-ballerina-crypto/compiler-plugin-tests/src/test/resources/static_code_analyzer/ballerina_packages/rule2/pbkdf2_func.bal" + }, + { + "location": { + "filePath": "pbkdf2_func.bal", + "startLine": 22, + "endLine": 22, + "startColumn": 14, + "endColumn": 68, + "startOffset": 928, + "length": 54 + }, + "rule": { + "id": "ballerina/crypto:2", + "numericId": 2, + "description": "Avoid using fast hashing algorithms", + "ruleKind": "VULNERABILITY" + }, + "source": "BUILT_IN", + "fileName": "rule2/pbkdf2_func.bal", + "filePath": "module-ballerina-crypto/compiler-plugin-tests/src/test/resources/static_code_analyzer/ballerina_packages/rule2/pbkdf2_func.bal" + }, + { + "location": { + "filePath": "pbkdf2_func.bal", + "startLine": 23, + "endLine": 23, + "startColumn": 14, + "endColumn": 70, + "startOffset": 998, + "length": 56 + }, + "rule": { + "id": "ballerina/crypto:2", + "numericId": 2, + "description": "Avoid using fast hashing algorithms", + "ruleKind": "VULNERABILITY" + }, + "source": "BUILT_IN", + "fileName": "rule2/pbkdf2_func.bal", + "filePath": "module-ballerina-crypto/compiler-plugin-tests/src/test/resources/static_code_analyzer/ballerina_packages/rule2/pbkdf2_func.bal" + }, + { + "location": { + "filePath": "pbkdf2_func.bal", + "startLine": 24, + "endLine": 24, + "startColumn": 14, + "endColumn": 70, + "startOffset": 1070, + "length": 56 + }, + "rule": { + "id": "ballerina/crypto:2", + "numericId": 2, + "description": "Avoid using fast hashing algorithms", + "ruleKind": "VULNERABILITY" + }, + "source": "BUILT_IN", + "fileName": "rule2/pbkdf2_func.bal", + "filePath": "module-ballerina-crypto/compiler-plugin-tests/src/test/resources/static_code_analyzer/ballerina_packages/rule2/pbkdf2_func.bal" + }, + { + "location": { + "filePath": "pbkdf2_func.bal", + "startLine": 38, + "endLine": 38, + "startColumn": 14, + "endColumn": 66, + "startOffset": 1665, + "length": 52 + }, + "rule": { + "id": "ballerina/crypto:2", + "numericId": 2, + "description": "Avoid using fast hashing algorithms", + "ruleKind": "VULNERABILITY" + }, + "source": "BUILT_IN", + "fileName": "rule2/pbkdf2_func.bal", + "filePath": "module-ballerina-crypto/compiler-plugin-tests/src/test/resources/static_code_analyzer/ballerina_packages/rule2/pbkdf2_func.bal" + }, + { + "location": { + "filePath": "pbkdf2_func.bal", + "startLine": 39, + "endLine": 39, + "startColumn": 14, + "endColumn": 59, + "startOffset": 1733, + "length": 45 + }, + "rule": { + "id": "ballerina/crypto:2", + "numericId": 2, + "description": "Avoid using fast hashing algorithms", + "ruleKind": "VULNERABILITY" + }, + "source": "BUILT_IN", + "fileName": "rule2/pbkdf2_func.bal", + "filePath": "module-ballerina-crypto/compiler-plugin-tests/src/test/resources/static_code_analyzer/ballerina_packages/rule2/pbkdf2_func.bal" + }, + { + "location": { + "filePath": "pbkdf2_func.bal", + "startLine": 40, + "endLine": 40, + "startColumn": 14, + "endColumn": 66, + "startOffset": 1794, + "length": 52 + }, + "rule": { + "id": "ballerina/crypto:2", + "numericId": 2, + "description": "Avoid using fast hashing algorithms", + "ruleKind": "VULNERABILITY" + }, + "source": "BUILT_IN", + "fileName": "rule2/pbkdf2_func.bal", + "filePath": "module-ballerina-crypto/compiler-plugin-tests/src/test/resources/static_code_analyzer/ballerina_packages/rule2/pbkdf2_func.bal" + }, { "location": { "filePath": "pbkdf2_func_var_named_arg.bal", diff --git a/compiler-plugin-tests/src/test/resources/static_code_analyzer/expected_output/rule3.json b/compiler-plugin-tests/src/test/resources/static_code_analyzer/expected_output/rule3.json index cfde788c..fb101468 100644 --- a/compiler-plugin-tests/src/test/resources/static_code_analyzer/expected_output/rule3.json +++ b/compiler-plugin-tests/src/test/resources/static_code_analyzer/expected_output/rule3.json @@ -1,4 +1,44 @@ [ + { + "location": { + "filePath": "func_hardcoded_iv_param.bal", + "startLine": 21, + "endLine": 21, + "startColumn": 0, + "endColumn": 6, + "startOffset": 746, + "length": 6 + }, + "rule": { + "id": "ballerina:3", + "numericId": 3, + "description": "Non isolated public function", + "ruleKind": "CODE_SMELL" + }, + "source": "BUILT_IN", + "fileName": "rule3/func_hardcoded_iv_param.bal", + "filePath": "module-ballerina-crypto/compiler-plugin-tests/src/test/resources/static_code_analyzer/ballerina_packages/rule3/func_hardcoded_iv_param.bal" + }, + { + "location": { + "filePath": "func_hardcoded_iv_param.bal", + "startLine": 46, + "endLine": 46, + "startColumn": 0, + "endColumn": 6, + "startOffset": 1681, + "length": 6 + }, + "rule": { + "id": "ballerina:3", + "numericId": 3, + "description": "Non isolated public function", + "ruleKind": "CODE_SMELL" + }, + "source": "BUILT_IN", + "fileName": "rule3/func_hardcoded_iv_param.bal", + "filePath": "module-ballerina-crypto/compiler-plugin-tests/src/test/resources/static_code_analyzer/ballerina_packages/rule3/func_hardcoded_iv_param.bal" + }, { "location": { "filePath": "func_var_named_arg.bal", @@ -119,6 +159,126 @@ "fileName": "rule3/mod_var_pos_arg.bal", "filePath": "module-ballerina-crypto/compiler-plugin-tests/src/test/resources/static_code_analyzer/ballerina_packages/rule3/mod_var_pos_arg.bal" }, + { + "location": { + "filePath": "func_hardcoded_iv_param.bal", + "startLine": 24, + "endLine": 24, + "startColumn": 14, + "endColumn": 107, + "startOffset": 937, + "length": 93 + }, + "rule": { + "id": "ballerina/crypto:3", + "numericId": 3, + "description": "Avoid reusing counter mode initialization vectors", + "ruleKind": "VULNERABILITY" + }, + "source": "BUILT_IN", + "fileName": "rule3/func_hardcoded_iv_param.bal", + "filePath": "module-ballerina-crypto/compiler-plugin-tests/src/test/resources/static_code_analyzer/ballerina_packages/rule3/func_hardcoded_iv_param.bal" + }, + { + "location": { + "filePath": "func_hardcoded_iv_param.bal", + "startLine": 26, + "endLine": 26, + "startColumn": 14, + "endColumn": 79, + "startOffset": 1047, + "length": 65 + }, + "rule": { + "id": "ballerina/crypto:3", + "numericId": 3, + "description": "Avoid reusing counter mode initialization vectors", + "ruleKind": "VULNERABILITY" + }, + "source": "BUILT_IN", + "fileName": "rule3/func_hardcoded_iv_param.bal", + "filePath": "module-ballerina-crypto/compiler-plugin-tests/src/test/resources/static_code_analyzer/ballerina_packages/rule3/func_hardcoded_iv_param.bal" + }, + { + "location": { + "filePath": "func_hardcoded_iv_param.bal", + "startLine": 29, + "endLine": 29, + "startColumn": 14, + "endColumn": 54, + "startOffset": 1209, + "length": 40 + }, + "rule": { + "id": "ballerina/crypto:3", + "numericId": 3, + "description": "Avoid reusing counter mode initialization vectors", + "ruleKind": "VULNERABILITY" + }, + "source": "BUILT_IN", + "fileName": "rule3/func_hardcoded_iv_param.bal", + "filePath": "module-ballerina-crypto/compiler-plugin-tests/src/test/resources/static_code_analyzer/ballerina_packages/rule3/func_hardcoded_iv_param.bal" + }, + { + "location": { + "filePath": "func_hardcoded_iv_param.bal", + "startLine": 32, + "endLine": 32, + "startColumn": 14, + "endColumn": 54, + "startOffset": 1307, + "length": 40 + }, + "rule": { + "id": "ballerina/crypto:3", + "numericId": 3, + "description": "Avoid reusing counter mode initialization vectors", + "ruleKind": "VULNERABILITY" + }, + "source": "BUILT_IN", + "fileName": "rule3/func_hardcoded_iv_param.bal", + "filePath": "module-ballerina-crypto/compiler-plugin-tests/src/test/resources/static_code_analyzer/ballerina_packages/rule3/func_hardcoded_iv_param.bal" + }, + { + "location": { + "filePath": "func_hardcoded_iv_param.bal", + "startLine": 39, + "endLine": 39, + "startColumn": 14, + "endColumn": 54, + "startOffset": 1517, + "length": 40 + }, + "rule": { + "id": "ballerina/crypto:3", + "numericId": 3, + "description": "Avoid reusing counter mode initialization vectors", + "ruleKind": "VULNERABILITY" + }, + "source": "BUILT_IN", + "fileName": "rule3/func_hardcoded_iv_param.bal", + "filePath": "module-ballerina-crypto/compiler-plugin-tests/src/test/resources/static_code_analyzer/ballerina_packages/rule3/func_hardcoded_iv_param.bal" + }, + { + "location": { + "filePath": "func_hardcoded_iv_param.bal", + "startLine": 42, + "endLine": 42, + "startColumn": 18, + "endColumn": 58, + "startOffset": 1630, + "length": 40 + }, + "rule": { + "id": "ballerina/crypto:3", + "numericId": 3, + "description": "Avoid reusing counter mode initialization vectors", + "ruleKind": "VULNERABILITY" + }, + "source": "BUILT_IN", + "fileName": "rule3/func_hardcoded_iv_param.bal", + "filePath": "module-ballerina-crypto/compiler-plugin-tests/src/test/resources/static_code_analyzer/ballerina_packages/rule3/func_hardcoded_iv_param.bal" + }, { "location": { "filePath": "func_var_named_arg.bal", diff --git a/compiler-plugin/src/main/java/io/ballerina/stdlib/crypto/compiler/staticcodeanalyzer/CryptoAnalyzerUtils.java b/compiler-plugin/src/main/java/io/ballerina/stdlib/crypto/compiler/staticcodeanalyzer/CryptoAnalyzerUtils.java index 261cfaa7..f197e7b3 100644 --- a/compiler-plugin/src/main/java/io/ballerina/stdlib/crypto/compiler/staticcodeanalyzer/CryptoAnalyzerUtils.java +++ b/compiler-plugin/src/main/java/io/ballerina/stdlib/crypto/compiler/staticcodeanalyzer/CryptoAnalyzerUtils.java @@ -34,7 +34,6 @@ import io.ballerina.compiler.syntax.tree.FunctionArgumentNode; import io.ballerina.compiler.syntax.tree.FunctionBodyBlockNode; import io.ballerina.compiler.syntax.tree.FunctionCallExpressionNode; -import io.ballerina.compiler.syntax.tree.IdentifierToken; import io.ballerina.compiler.syntax.tree.ModuleMemberDeclarationNode; import io.ballerina.compiler.syntax.tree.ModulePartNode; import io.ballerina.compiler.syntax.tree.ModuleVariableDeclarationNode; @@ -44,6 +43,7 @@ import io.ballerina.compiler.syntax.tree.NodeList; import io.ballerina.compiler.syntax.tree.PositionalArgumentNode; import io.ballerina.compiler.syntax.tree.SeparatedNodeList; +import io.ballerina.compiler.syntax.tree.SimpleNameReferenceNode; import io.ballerina.compiler.syntax.tree.StatementNode; import io.ballerina.compiler.syntax.tree.SyntaxKind; import io.ballerina.compiler.syntax.tree.VariableDeclarationNode; @@ -339,8 +339,8 @@ public static void processStatementsForVariableExpressions(NodeList varExpressions) { Node varRef = assignmentNode.varRef(); - if (varRef instanceof IdentifierToken variableNameIdentifier) { - String varName = unescapeIdentifier(variableNameIdentifier.text()); + if (varRef instanceof SimpleNameReferenceNode simpleNameRef) { + String varName = unescapeIdentifier(simpleNameRef.name().text()); ExpressionNode expression = assignmentNode.expression(); varExpressions.put(varName, expression); } diff --git a/compiler-plugin/src/main/java/io/ballerina/stdlib/crypto/compiler/staticcodeanalyzer/FunctionContext.java b/compiler-plugin/src/main/java/io/ballerina/stdlib/crypto/compiler/staticcodeanalyzer/FunctionContext.java index 18ef6340..a6c7d77e 100644 --- a/compiler-plugin/src/main/java/io/ballerina/stdlib/crypto/compiler/staticcodeanalyzer/FunctionContext.java +++ b/compiler-plugin/src/main/java/io/ballerina/stdlib/crypto/compiler/staticcodeanalyzer/FunctionContext.java @@ -215,7 +215,7 @@ public Optional getParamExpression(String paramName) { * @param varName The name of the variable * @return An Optional containing the expression node if found, otherwise empty */ - private Optional getVarExpression(String varName) { + public Optional getVarExpression(String varName) { varName = unescapeIdentifier(varName); if (!varExpressions.containsKey(varName)) { return Optional.empty(); diff --git a/compiler-plugin/src/main/java/io/ballerina/stdlib/crypto/compiler/staticcodeanalyzer/functionrules/AvoidFastHashAlgorithmsRule.java b/compiler-plugin/src/main/java/io/ballerina/stdlib/crypto/compiler/staticcodeanalyzer/functionrules/AvoidFastHashAlgorithmsRule.java index 4eb17c60..57f3b059 100644 --- a/compiler-plugin/src/main/java/io/ballerina/stdlib/crypto/compiler/staticcodeanalyzer/functionrules/AvoidFastHashAlgorithmsRule.java +++ b/compiler-plugin/src/main/java/io/ballerina/stdlib/crypto/compiler/staticcodeanalyzer/functionrules/AvoidFastHashAlgorithmsRule.java @@ -20,6 +20,7 @@ import io.ballerina.compiler.api.SemanticModel; import io.ballerina.compiler.api.symbols.ConstantSymbol; import io.ballerina.compiler.api.symbols.Symbol; +import io.ballerina.compiler.api.values.ConstantValue; import io.ballerina.compiler.syntax.tree.BasicLiteralNode; import io.ballerina.compiler.syntax.tree.ExpressionNode; import io.ballerina.compiler.syntax.tree.NameReferenceNode; @@ -54,7 +55,6 @@ public class AvoidFastHashAlgorithmsRule implements CryptoFunctionRule { public static final int BCRYPT_RECOMMENDED_WORK_FACTOR = 10; public static final int ARGON2_RECOMMENDED_ITERATIONS = 2; public static final int ARGON2_RECOMMENDED_MEMORY = 19456; - public static final int ARGON2_RECOMMENDED_PARALLELISM = 1; public static final int PBKDF2_RECOMMENDED_ITERATIONS_FOR_SHA1 = 1300000; public static final int PBKDF2_RECOMMENDED_ITERATIONS_FOR_SHA256 = 600000; public static final int PBKDF2_RECOMMENDED_ITERATIONS_FOR_SHA512 = 210000; @@ -98,9 +98,10 @@ private boolean isArgon2WithWeakParams(FunctionContext context) { } SemanticModel semanticModel = context.semanticModel(); // Check if any parameter is below the recommended threshold + // Parallelism should be a positive integer value and the minimum recommended value is 1 + // So, we do not need to check for parallelism here return isArgon2ParamBelowThreshold(context, ITERATIONS, ARGON2_RECOMMENDED_ITERATIONS, semanticModel) || - isArgon2ParamBelowThreshold(context, MEMORY, ARGON2_RECOMMENDED_MEMORY, semanticModel) || - isArgon2ParamBelowThreshold(context, PARALLELISM, ARGON2_RECOMMENDED_PARALLELISM, semanticModel); + isArgon2ParamBelowThreshold(context, MEMORY, ARGON2_RECOMMENDED_MEMORY, semanticModel); } private boolean isArgon2ParamBelowThreshold(FunctionContext context, String paramName, @@ -154,9 +155,10 @@ private static boolean hasLowerIntegerValue(ExpressionNode valueExpr, Integer ta } } else if (valueExpr instanceof NameReferenceNode refNode) { Optional refSymbol = semanticModel.symbol(refNode); - if (refSymbol.isPresent() && refSymbol.get() instanceof ConstantSymbol constantRef) { - return constantRef.constValue() instanceof Integer && - ((Integer) constantRef.constValue()) < targetValue; + if (refSymbol.isPresent() && refSymbol.get() instanceof ConstantSymbol constantRef && + constantRef.constValue() instanceof ConstantValue constantValue && + constantValue.value() instanceof Long longValue) { + return longValue < targetValue; } } return false; diff --git a/compiler-plugin/src/main/java/io/ballerina/stdlib/crypto/compiler/staticcodeanalyzer/functionrules/AvoidReusingCounterModeVectorsRule.java b/compiler-plugin/src/main/java/io/ballerina/stdlib/crypto/compiler/staticcodeanalyzer/functionrules/AvoidReusingCounterModeVectorsRule.java index 6b415c3f..ce323e0c 100644 --- a/compiler-plugin/src/main/java/io/ballerina/stdlib/crypto/compiler/staticcodeanalyzer/functionrules/AvoidReusingCounterModeVectorsRule.java +++ b/compiler-plugin/src/main/java/io/ballerina/stdlib/crypto/compiler/staticcodeanalyzer/functionrules/AvoidReusingCounterModeVectorsRule.java @@ -17,6 +17,8 @@ */ package io.ballerina.stdlib.crypto.compiler.staticcodeanalyzer.functionrules; +import io.ballerina.compiler.api.symbols.Symbol; +import io.ballerina.compiler.api.symbols.SymbolKind; import io.ballerina.compiler.syntax.tree.ExpressionNode; import io.ballerina.compiler.syntax.tree.ListConstructorExpressionNode; import io.ballerina.compiler.syntax.tree.MethodCallExpressionNode; @@ -27,6 +29,7 @@ import java.util.Optional; +import static io.ballerina.stdlib.crypto.compiler.staticcodeanalyzer.CryptoAnalyzerUtils.unescapeIdentifier; import static io.ballerina.stdlib.crypto.compiler.staticcodeanalyzer.CryptoRule.AVOID_REUSING_COUNTER_MODE_VECTORS; /** @@ -51,7 +54,7 @@ public void analyze(FunctionContext context) { + context.functionName()); } - if (hasHardCodedIV(paramExpression.get())) { + if (hasHardCodedIV(paramExpression.get(), context)) { context.reporter().reportIssue(context.document(), context.functionLocation(), getRuleId()); } } @@ -68,19 +71,17 @@ public boolean isApplicable(FunctionContext context) { return functionName.equals(ENCRYPT_AES_CBC) || functionName.equals(ENCRYPT_AES_GCM); } - private boolean hasHardCodedIV(ExpressionNode ivExpression) { + private boolean hasHardCodedIV(ExpressionNode ivExpression, FunctionContext context) { // Check for list constructor with numeric literals (e.g., [1, 2, 3, ...]) if (ivExpression instanceof ListConstructorExpressionNode listExpression) { return listExpression.expressions().stream() .allMatch(expr -> expr.kind().equals(SyntaxKind.NUMERIC_LITERAL)); } - // Check for toBytes() method called on a string literal or name reference + // Check for toBytes() method called on a string literal or name reference referring to a constant if (ivExpression instanceof MethodCallExpressionNode methodCallExpression) { ExpressionNode expression = methodCallExpression.expression(); - if (!expression.kind().equals(SyntaxKind.STRING_LITERAL) && - !expression.kind().equals(SyntaxKind.SIMPLE_NAME_REFERENCE) && - !expression.kind().equals(SyntaxKind.QUALIFIED_NAME_REFERENCE)) { + if (!isMethodCallOnConstantExpr(expression, context)) { return false; } NameReferenceNode nameReferenceNode = methodCallExpression.methodName(); @@ -91,4 +92,27 @@ private boolean hasHardCodedIV(ExpressionNode ivExpression) { return false; } + + private boolean isMethodCallOnConstantExpr(ExpressionNode expression, FunctionContext context) { + if (expression.kind().equals(SyntaxKind.STRING_LITERAL)) { + return true; + } + if (expression.kind().equals(SyntaxKind.SIMPLE_NAME_REFERENCE) || + expression.kind().equals(SyntaxKind.QUALIFIED_NAME_REFERENCE)) { + Optional symbol = context.semanticModel().symbol(expression); + if (symbol.isPresent() && symbol.get().kind().equals(SymbolKind.CONSTANT)) { + return true; + } + } + + // If the value is not constant, check if it's a parameter referring to a constant where the value can be + // determined at compile time + if (expression instanceof SimpleNameReferenceNode simpleNameRef) { + String varName = unescapeIdentifier(simpleNameRef.name().text()); + Optional paramExpression = context.getVarExpression(varName); + return paramExpression.isPresent() && + isMethodCallOnConstantExpr(paramExpression.get(), context); + } + return false; + } } diff --git a/docs/spec/spec.md b/docs/spec/spec.md index 31b8086f..8de49758 100644 --- a/docs/spec/spec.md +++ b/docs/spec/spec.md @@ -1252,11 +1252,11 @@ boolean isValid = check crypto:verifyPbkdf2(password, hashedPassword); The following static code rules are applied to the Crypto module. -| Id | Kind | Description | -|--------------------|---------------|----------------------------------------------------------------------| -| ballerina/crypto:1 | VULNERABILITY | Avoid using insecure cipher modes or padding schemes | -| ballerina/crypto:2 | VULNERABILITY | Avoid using fast hashing algorithms | -| ballerina/crypto:3 | VULNERABILITY | Avoid reusing counter mode initialization vectors | +| Id | Kind | Description | +|--------------------|---------------|-------------------------------------------------------------------------------------------------------------------| +| ballerina/crypto:1 | VULNERABILITY | [Avoid using insecure cipher modes or padding schemes](#111-avoid-using-insecure-cipher-modes-or-padding-schemes) | +| ballerina/crypto:2 | VULNERABILITY | [Avoid using fast hashing algorithms](#112-avoid-using-fast-hashing-algorithms) | +| ballerina/crypto:3 | VULNERABILITY | [Avoid reusing counter mode initialization vectors](#113-avoid-reusing-counter-mode-initialization-vectors) | ### 11.1 Avoid using insecure cipher modes or padding schemes @@ -1366,7 +1366,7 @@ For Argon2: - Use the Argon2id variant (which Ballerina implements) - Minimum configuration of 19 MiB (19,456 KB) of memory - An iteration count of at least 2 -- At least 1 degree of parallelism +- At least 1 degree of parallelism (this is enforced by Ballerina) For PBKDF2: From c8c6a54b84a54e088876ecc19e7863a0b2d6f5a3 Mon Sep 17 00:00:00 2001 From: TharmiganK Date: Mon, 20 Oct 2025 13:08:59 +0530 Subject: [PATCH 09/10] Update AES encryption example from CCM to CBC --- docs/spec/spec.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/docs/spec/spec.md b/docs/spec/spec.md index 8de49758..83ff6bbc 100644 --- a/docs/spec/spec.md +++ b/docs/spec/spec.md @@ -1548,7 +1548,7 @@ public function encryptData(string data) returns [byte[], byte[16]]|error { This compliant approach generates a cryptographically secure random initialization vector for each encryption operation and returns it along with the encrypted data. The IV must be stored alongside the encrypted data (but doesn't need to be kept secret) to allow for decryption later. -### 11.3.3.2 AES-CCM Encryption Example +### 11.3.3.2 AES-CBC Encryption Example **Non-compliant code:** @@ -1558,7 +1558,7 @@ public function encryptMessage(string message) returns byte[]|error { byte[12] nonce = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1]; byte[16] key = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16]; byte[] messageBytes = message.toBytes(); - return crypto:encryptAesCcm(messageBytes, key, nonce); + return crypto:encryptAesCbc(messageBytes, key, nonce); } ``` @@ -1576,7 +1576,7 @@ public function encryptMessage(string message) returns [byte[], byte[12]]|error } byte[16] key = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16]; byte[] messageBytes = message.toBytes(); - byte[] encryptedData = check crypto:encryptAesCcm(messageBytes, key, nonce); + byte[] encryptedData = check crypto:encryptAesCbc(messageBytes, key, nonce); return [encryptedData, nonce]; } ``` From cbb204bb595e28b9260ef4224a5fbd92b0efa6c6 Mon Sep 17 00:00:00 2001 From: TharmiganK Date: Tue, 21 Oct 2025 08:57:14 +0530 Subject: [PATCH 10/10] Fix reference to SHA256 in password hashing function --- docs/spec/spec.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/spec/spec.md b/docs/spec/spec.md index 83ff6bbc..4d5ab359 100644 --- a/docs/spec/spec.md +++ b/docs/spec/spec.md @@ -1463,7 +1463,7 @@ Using PBKDF2 with insufficient iterations (default 10,000) is vulnerable to brut public function hashPassword() returns error? { string password = "mySecurePassword123"; // Using sufficient iterations as recommended by NIST - string hashedPassword = check crypto:hashPbkdf2(password, iterations = 600000, algorithm = SHA256); + string hashedPassword = check crypto:hashPbkdf2(password, iterations = 600000, algorithm = crypto:SHA256); io:println("Hashed Password: ", hashedPassword); } ```