From 71781c769841e885e2ba1e5d9c065046fce9a5d2 Mon Sep 17 00:00:00 2001 From: Nureka Rodrigo Date: Fri, 23 May 2025 14:23:42 +0530 Subject: [PATCH 1/2] Add new scan rules --- .gitignore | 2 + ballerina/Ballerina.toml | 6 +- ballerina/CompilerPlugin.toml | 6 + ballerina/Dependencies.toml | 2 +- ballerina/build.gradle | 10 +- build-config/resources/CompilerPlugin.toml | 6 + changelog.md | 7 + compiler-plugin-tests/build.gradle | 140 ++++ .../StaticCodeAnalyzerTest.java | 261 +++++++ .../ballerina_packages/rule1/Ballerina.toml | 8 + .../ballerina_packages/rule1/aes_cbc.bal | 32 + .../rule1/aes_cbc_as_import.bal | 32 + .../ballerina_packages/rule1/aes_ecb.bal | 28 + .../rule1/aes_ecb_as_import.bal | 28 + .../rule1/weak_cipher_algo.bal | 46 ++ .../ballerina_packages/rule2/Ballerina.toml | 8 + .../ballerina_packages/rule2/argon_func.bal | 42 ++ .../rule2/argon_func_var_named_arg.bal | 25 + .../rule2/argon_func_var_pos_arg.bal | 25 + .../rule2/argon_inline_named_arg.bal | 22 + .../rule2/argon_inline_pos_arg.bal | 22 + .../rule2/argon_mod_var_named_arg.bal | 26 + .../rule2/argon_mod_var_pos_arg.bal | 26 + .../rule2/bcrypt_func_var_named_arg.bal | 23 + .../rule2/bcrypt_func_var_pos_arg.bal | 23 + .../rule2/bcrypt_inline_named_arg.bal | 22 + .../rule2/bcrypt_inline_pos_arg.bal | 22 + .../rule2/bcrypt_mod_var_named_arg.bal | 24 + .../rule2/bcrypt_mod_var_pos_arg.bal | 24 + .../rule2/modules/module/module.bal | 20 + .../ballerina_packages/rule3/Ballerina.toml | 8 + .../rule3/func_hardcoded_iv_param.bal | 61 ++ .../rule3/func_var_named_arg.bal | 24 + .../rule3/func_var_pos_arg.bal | 24 + .../rule3/inline_named_arg.bal | 23 + .../rule3/inline_pos_arg.bal | 23 + .../rule3/mod_var_named_arg.bal | 25 + .../rule3/mod_var_pos_arg.bal | 25 + .../rule3/modules/module/module.bal | 19 + .../expected_output/rule1.json | 282 +++++++ .../expected_output/rule2.json | 582 +++++++++++++++ .../expected_output/rule3.json | 402 ++++++++++ .../src/test/resources/testng.xml | 29 + compiler-plugin/build.gradle | 78 ++ .../crypto/compiler/CryptoCompilerPlugin.java | 41 + .../CryptoAnalyzerUtils.java | 369 +++++++++ .../CryptoCipherAlgorithmAnalyzer.java | 71 ++ .../CryptoCodeAnalyzer.java | 38 + .../CryptoFunctionRulesEngine.java | 62 ++ .../staticcodeanalyzer/CryptoRule.java | 53 ++ .../staticcodeanalyzer/FunctionContext.java | 230 ++++++ .../staticcodeanalyzer/RuleFactory.java | 35 + .../compiler/staticcodeanalyzer/RuleImpl.java | 54 ++ .../AvoidFastHashAlgorithmsRule.java | 125 ++++ .../AvoidReusingCounterModeVectorsRule.java | 118 +++ .../AvoidWeakCipherAlgorithmsRule.java | 69 ++ .../functionrules/CryptoFunctionRule.java | 50 ++ .../src/main/java/module-info.java | 24 + compiler-plugin/src/main/resources/rules.json | 17 + docs/spec/spec.md | 699 +++++++++++++----- gradle.properties | 6 +- native/build.gradle | 2 +- native/src/main/java/module-info.java | 3 +- settings.gradle | 4 + 64 files changed, 4445 insertions(+), 198 deletions(-) create mode 100644 ballerina/CompilerPlugin.toml create mode 100644 build-config/resources/CompilerPlugin.toml create mode 100644 compiler-plugin-tests/build.gradle create mode 100644 compiler-plugin-tests/src/test/java/io/ballerina/stdlib/crypto/compiler/staticcodeanalyzer/StaticCodeAnalyzerTest.java create mode 100644 compiler-plugin-tests/src/test/resources/static_code_analyzer/ballerina_packages/rule1/Ballerina.toml create mode 100644 compiler-plugin-tests/src/test/resources/static_code_analyzer/ballerina_packages/rule1/aes_cbc.bal create mode 100644 compiler-plugin-tests/src/test/resources/static_code_analyzer/ballerina_packages/rule1/aes_cbc_as_import.bal create mode 100644 compiler-plugin-tests/src/test/resources/static_code_analyzer/ballerina_packages/rule1/aes_ecb.bal create mode 100644 compiler-plugin-tests/src/test/resources/static_code_analyzer/ballerina_packages/rule1/aes_ecb_as_import.bal 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/Ballerina.toml 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/argon_func_var_named_arg.bal create mode 100644 compiler-plugin-tests/src/test/resources/static_code_analyzer/ballerina_packages/rule2/argon_func_var_pos_arg.bal create mode 100644 compiler-plugin-tests/src/test/resources/static_code_analyzer/ballerina_packages/rule2/argon_inline_named_arg.bal create mode 100644 compiler-plugin-tests/src/test/resources/static_code_analyzer/ballerina_packages/rule2/argon_inline_pos_arg.bal create mode 100644 compiler-plugin-tests/src/test/resources/static_code_analyzer/ballerina_packages/rule2/argon_mod_var_named_arg.bal create mode 100644 compiler-plugin-tests/src/test/resources/static_code_analyzer/ballerina_packages/rule2/argon_mod_var_pos_arg.bal create mode 100644 compiler-plugin-tests/src/test/resources/static_code_analyzer/ballerina_packages/rule2/bcrypt_func_var_named_arg.bal create mode 100644 compiler-plugin-tests/src/test/resources/static_code_analyzer/ballerina_packages/rule2/bcrypt_func_var_pos_arg.bal create mode 100644 compiler-plugin-tests/src/test/resources/static_code_analyzer/ballerina_packages/rule2/bcrypt_inline_named_arg.bal create mode 100644 compiler-plugin-tests/src/test/resources/static_code_analyzer/ballerina_packages/rule2/bcrypt_inline_pos_arg.bal create mode 100644 compiler-plugin-tests/src/test/resources/static_code_analyzer/ballerina_packages/rule2/bcrypt_mod_var_named_arg.bal create mode 100644 compiler-plugin-tests/src/test/resources/static_code_analyzer/ballerina_packages/rule2/bcrypt_mod_var_pos_arg.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/rule3/Ballerina.toml 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/func_var_named_arg.bal create mode 100644 compiler-plugin-tests/src/test/resources/static_code_analyzer/ballerina_packages/rule3/func_var_pos_arg.bal create mode 100644 compiler-plugin-tests/src/test/resources/static_code_analyzer/ballerina_packages/rule3/inline_named_arg.bal create mode 100644 compiler-plugin-tests/src/test/resources/static_code_analyzer/ballerina_packages/rule3/inline_pos_arg.bal create mode 100644 compiler-plugin-tests/src/test/resources/static_code_analyzer/ballerina_packages/rule3/mod_var_named_arg.bal create mode 100644 compiler-plugin-tests/src/test/resources/static_code_analyzer/ballerina_packages/rule3/mod_var_pos_arg.bal create mode 100644 compiler-plugin-tests/src/test/resources/static_code_analyzer/ballerina_packages/rule3/modules/module/module.bal create mode 100644 compiler-plugin-tests/src/test/resources/static_code_analyzer/expected_output/rule1.json create mode 100644 compiler-plugin-tests/src/test/resources/static_code_analyzer/expected_output/rule2.json create mode 100644 compiler-plugin-tests/src/test/resources/static_code_analyzer/expected_output/rule3.json create mode 100644 compiler-plugin-tests/src/test/resources/testng.xml create mode 100644 compiler-plugin/build.gradle create mode 100644 compiler-plugin/src/main/java/io/ballerina/stdlib/crypto/compiler/CryptoCompilerPlugin.java create mode 100644 compiler-plugin/src/main/java/io/ballerina/stdlib/crypto/compiler/staticcodeanalyzer/CryptoAnalyzerUtils.java create mode 100644 compiler-plugin/src/main/java/io/ballerina/stdlib/crypto/compiler/staticcodeanalyzer/CryptoCipherAlgorithmAnalyzer.java create mode 100644 compiler-plugin/src/main/java/io/ballerina/stdlib/crypto/compiler/staticcodeanalyzer/CryptoCodeAnalyzer.java 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/CryptoRule.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/RuleFactory.java create mode 100644 compiler-plugin/src/main/java/io/ballerina/stdlib/crypto/compiler/staticcodeanalyzer/RuleImpl.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 create mode 100644 compiler-plugin/src/main/java/module-info.java create mode 100644 compiler-plugin/src/main/resources/rules.json diff --git a/.gitignore b/.gitignore index 85e001f2..10e9dd00 100644 --- a/.gitignore +++ b/.gitignore @@ -37,3 +37,5 @@ target # Ballerina velocity.log* *Ballerina.lock + +compiler-plugin-tests/**/target diff --git a/ballerina/Ballerina.toml b/ballerina/Ballerina.toml index 9518c49f..44ce7d2c 100644 --- a/ballerina/Ballerina.toml +++ b/ballerina/Ballerina.toml @@ -1,7 +1,7 @@ [package] org = "ballerina" name = "crypto" -version = "2.9.1" +version = "2.9.2" 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.jar" +version = "2.9.2" +path = "../native/build/libs/crypto-native-2.9.2-SNAPSHOT.jar" [[platform.java21.dependency]] groupId = "org.bouncycastle" diff --git a/ballerina/CompilerPlugin.toml b/ballerina/CompilerPlugin.toml new file mode 100644 index 00000000..603da8e2 --- /dev/null +++ b/ballerina/CompilerPlugin.toml @@ -0,0 +1,6 @@ +[plugin] +id = "crypto-compiler-plugin" +class = "io.ballerina.stdlib.crypto.compiler.CryptoCompilerPlugin" + +[[dependency]] +path = "../compiler-plugin/build/libs/crypto-compiler-plugin-2.9.2-SNAPSHOT.jar" diff --git a/ballerina/Dependencies.toml b/ballerina/Dependencies.toml index 6488f734..47756594 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.9.2" dependencies = [ {org = "ballerina", name = "io"}, {org = "ballerina", name = "jballerina.java"}, diff --git a/ballerina/build.gradle b/ballerina/build.gradle index ba00d348..ccd6a1a1 100644 --- a/ballerina/build.gradle +++ b/ballerina/build.gradle @@ -28,6 +28,8 @@ def packageOrg = "ballerina" def tomlVersion = stripBallerinaExtensionVersion("${project.version}") def ballerinaTomlFile = new File("$project.projectDir/Ballerina.toml") def ballerinaTomlFilePlaceHolder = new File("${project.rootDir}/build-config/resources/Ballerina.toml") +def compilerPluginTomlFilePlaceHolder = new File("${project.rootDir}/build-config/resources/CompilerPlugin.toml") +def compilerPluginTomlFile = new File("$project.projectDir/CompilerPlugin.toml") def stripBallerinaExtensionVersion(String extVersion) { if (extVersion.matches(project.ext.timestampedVersionRegex)) { @@ -68,18 +70,20 @@ dependencies { } } -task updateTomlFiles { +tasks.register('updateTomlFiles') { doLast { def stdlibDependentBouncycastleVersion = project.bouncycastleVersion def newBallerinaToml = ballerinaTomlFilePlaceHolder.text.replace("@project.version@", project.version) newBallerinaToml = newBallerinaToml.replace("@toml.version@", tomlVersion) newBallerinaToml = newBallerinaToml.replace("@bouncycastle.version@", stdlibDependentBouncycastleVersion) + def newCompilerPluginToml = compilerPluginTomlFilePlaceHolder.text.replace("@project.version@", project.version) ballerinaTomlFile.text = newBallerinaToml + compilerPluginTomlFile.text = newCompilerPluginToml } } -task commitTomlFiles { +tasks.register('commitTomlFiles') { doLast { project.exec { ignoreExitValue true @@ -117,6 +121,8 @@ test.dependsOn ":${packageName}-native:build" build.dependsOn "generatePomFileForMavenPublication" build.dependsOn ":${packageName}-native:build" +build.dependsOn ":${packageName}-compiler-plugin:build" +test.dependsOn ":${packageName}-compiler-plugin:build" publishToMavenLocal.dependsOn build publish.dependsOn build diff --git a/build-config/resources/CompilerPlugin.toml b/build-config/resources/CompilerPlugin.toml new file mode 100644 index 00000000..4ed52ceb --- /dev/null +++ b/build-config/resources/CompilerPlugin.toml @@ -0,0 +1,6 @@ +[plugin] +id = "crypto-compiler-plugin" +class = "io.ballerina.stdlib.crypto.compiler.CryptoCompilerPlugin" + +[[dependency]] +path = "../compiler-plugin/build/libs/crypto-compiler-plugin-@project.version@.jar" diff --git a/changelog.md b/changelog.md index 76d58e36..5ffe7aca 100644 --- a/changelog.md +++ b/changelog.md @@ -3,6 +3,13 @@ This file contains all the notable changes done to the Ballerina Crypto package The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +## [Unreleased] + +### Added +- [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) + ## [2.9.1] - 2025-09-30 ### Fixed diff --git a/compiler-plugin-tests/build.gradle b/compiler-plugin-tests/build.gradle new file mode 100644 index 00000000..531f3e89 --- /dev/null +++ b/compiler-plugin-tests/build.gradle @@ -0,0 +1,140 @@ +/* + * 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. + */ + +plugins { + id 'java' + id 'checkstyle' + id 'com.github.spotbugs' + id 'jacoco' +} + +description = 'Ballerina - Crypto Compiler Plugin Tests' + +dependencies { + checkstyle project(':checkstyle') + checkstyle "com.puppycrawl.tools:checkstyle:${checkstylePluginVersion}" + + implementation project(':crypto-compiler-plugin') + + testImplementation group: 'org.ballerinalang', name: 'ballerina-lang', version: "${ballerinaLangVersion}" + testImplementation group: 'org.ballerinalang', name: 'ballerina-tools-api', version: "${ballerinaLangVersion}" + testImplementation group: 'org.ballerinalang', name: 'ballerina-parser', version: "${ballerinaLangVersion}" + testImplementation group: 'io.ballerina.scan', name: 'scan-command', version: "${balScanVersion}" + testImplementation group: 'io.ballerina.scan', name: 'scan-command-test-utils', version: "${balScanVersion}" + testImplementation group: 'org.testng', name: 'testng', version: "${testngVersion}" + testImplementation group: 'com.fasterxml.jackson.core', name: 'jackson-databind', version: "${jacksonDatabindVersion}" +} +tasks.withType(JavaCompile).configureEach { + options.encoding = 'UTF-8' +} + +sourceCompatibility = JavaVersion.VERSION_21 + +test { + systemProperty "ballerina.offline.flag", "true" + useTestNG() { + suites 'src/test/resources/testng.xml' + } + testLogging.showStandardStreams = true + testLogging { + events "PASSED", "FAILED", "SKIPPED" + afterSuite { desc, result -> + if (!desc.parent) { // will match the outermost suite + def output = "Results: ${result.resultType} (${result.testCount} tests, ${result.successfulTestCount} successes, ${result.failedTestCount} failures, ${result.skippedTestCount} skipped)" + def startItem = '| ', endItem = ' |' + def repeatLength = startItem.length() + output.length() + endItem.length() + println('\n' + ('-' * repeatLength) + '\n' + startItem + output + endItem + '\n' + ('-' * repeatLength)) + } + } + } + jacoco { + destinationFile = file("$buildDir/jacoco/test.exec") + } + finalizedBy jacocoTestReport +} + +jacocoTestReport { + dependsOn test + reports { + xml.required = true + } + sourceSets project(':crypto-compiler-plugin').sourceSets.main +} + +spotbugsTest { + def classLoader = plugins["com.github.spotbugs"].class.classLoader + def SpotBugsConfidence = classLoader.findLoadedClass("com.github.spotbugs.snom.Confidence") + def SpotBugsEffort = classLoader.findLoadedClass("com.github.spotbugs.snom.Effort") + ignoreFailures = true + effort = SpotBugsEffort.MAX + reportLevel = SpotBugsConfidence.LOW + reportsDir = file("$project.buildDir/reports/spotbugs") + def excludeFile = file("${rootDir}/build-config/spotbugs-exclude.xml") + if (excludeFile.exists()) { + it.excludeFilter = excludeFile + } + reports { + text.enabled = true + } +} + +spotbugsMain { + enabled false +} + +tasks.register('validateSpotbugs') { + doLast { + if (spotbugsMain.reports.size() > 0 && + spotbugsMain.reports[0].destination.exists() && + spotbugsMain.reports[0].destination.text.readLines().size() > 0) { + spotbugsMain.reports[0].destination?.eachLine { + println 'Failure: ' + it + } + throw new GradleException("Spotbugs rule violations were found."); + } + } +} + +tasks.withType(Checkstyle).configureEach { + exclude '**/module-info.java' +} + +checkstyle { + toolVersion "${project.checkstylePluginVersion}" + configFile rootProject.file("build-config/checkstyle/build/checkstyle.xml") + configProperties = ["suppressionFile": file("${rootDir}/build-config/checkstyle/build/suppressions.xml")] +} + +checkstyleMain { + enabled false +} + +spotbugsTest.finalizedBy validateSpotbugs +checkstyleTest.dependsOn ':checkstyle:downloadCheckstyleRuleFiles' + +compileJava { + doFirst { + options.compilerArgs = [ + '--module-path', classpath.asPath, + ] + classpath = files() + } +} + +test.dependsOn ":crypto-ballerina:build" +build.dependsOn ":crypto-ballerina:build" 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 new file mode 100644 index 00000000..3bf9b637 --- /dev/null +++ b/compiler-plugin-tests/src/test/java/io/ballerina/stdlib/crypto/compiler/staticcodeanalyzer/StaticCodeAnalyzerTest.java @@ -0,0 +1,261 @@ +/* + * 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 com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.SerializationFeature; +import io.ballerina.projects.Project; +import io.ballerina.projects.ProjectEnvironmentBuilder; +import io.ballerina.projects.directory.BuildProject; +import io.ballerina.projects.environment.Environment; +import io.ballerina.projects.environment.EnvironmentBuilder; +import io.ballerina.scan.Issue; +import io.ballerina.scan.Rule; +import io.ballerina.scan.Source; +import io.ballerina.scan.test.Assertions; +import io.ballerina.scan.test.TestOptions; +import io.ballerina.scan.test.TestRunner; +import org.testng.Assert; +import org.testng.annotations.Test; + +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.PrintStream; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.Arrays; +import java.util.List; +import java.util.Locale; +import java.util.stream.Collectors; + +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_WEAK_CIPHER_ALGORITHMS; +import static java.nio.charset.StandardCharsets.UTF_8; + +public class StaticCodeAnalyzerTest { + private static final Path RESOURCE_PACKAGES_DIRECTORY = Paths + .get("src", "test", "resources", "static_code_analyzer", "ballerina_packages").toAbsolutePath(); + private static final Path EXPECTED_OUTPUT_DIRECTORY = Paths + .get("src", "test", "resources", "static_code_analyzer", "expected_output").toAbsolutePath(); + private static final Path JSON_RULES_FILE_PATH = Paths + .get("../", "compiler-plugin", "src", "main", "resources", "rules.json").toAbsolutePath(); + private static final Path DISTRIBUTION_PATH = Paths.get("../", "target", "ballerina-runtime"); + private static final String MODULE_BALLERINA_CRYPTO = "module-ballerina-crypto"; + + @Test + public void validateRulesJson() throws IOException { + String expectedRules = "[" + Arrays.stream(CryptoRule.values()) + .map(CryptoRule::toString).collect(Collectors.joining(",")) + "]"; + String actualRules = Files.readString(JSON_RULES_FILE_PATH); + assertJsonEqual(actualRules, expectedRules); + } + + @Test + public void testStaticCodeRulesWithAPI() throws IOException { + ByteArrayOutputStream console = new ByteArrayOutputStream(); + PrintStream printStream = new PrintStream(console, true, UTF_8); + + for (CryptoRule rule : CryptoRule.values()) { + testIndividualRule(rule, console, printStream); + } + } + + private void testIndividualRule(CryptoRule rule, ByteArrayOutputStream console, PrintStream printStream) + throws IOException { + String targetPackageName = "rule" + rule.getId(); + Path targetPackagePath = RESOURCE_PACKAGES_DIRECTORY.resolve(targetPackageName); + + TestRunner testRunner = setupTestRunner(targetPackagePath, printStream); + testRunner.performScan(); + + validateRules(testRunner.getRules()); + validateIssues(rule, testRunner.getIssues()); + validateOutput(console, targetPackageName); + + console.reset(); + } + + private TestRunner setupTestRunner(Path targetPackagePath, PrintStream printStream) { + Project project = BuildProject.load(getEnvironmentBuilder(), targetPackagePath); + TestOptions options = TestOptions.builder(project).setOutputStream(printStream).build(); + return new TestRunner(options); + } + + private void validateRules(List rules) { + Assertions.assertRule( + rules, + "ballerina/crypto:1", + AVOID_WEAK_CIPHER_ALGORITHMS.getDescription(), + VULNERABILITY); + Assertions.assertRule( + rules, + "ballerina/crypto:2", + AVOID_FAST_HASH_ALGORITHMS.getDescription(), + VULNERABILITY); + Assertions.assertRule( + rules, + "ballerina/crypto:3", + AVOID_REUSING_COUNTER_MODE_VECTORS.getDescription(), + VULNERABILITY); + } + + private void validateIssues(CryptoRule rule, List issues) { + int index; + switch (rule) { + case AVOID_WEAK_CIPHER_ALGORITHMS: + 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, index++, "ballerina/crypto:1", "aes_cbc_as_import.bal", + 30, 30, Source.BUILT_IN); + Assertions.assertIssue(issues, index++, "ballerina/crypto:1", "aes_ecb.bal", + 26, 26, Source.BUILT_IN); + 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(), 16); + 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, index++, "ballerina/crypto:2", "argon_func_var_pos_arg.bal", + 23, 23, Source.BUILT_IN); + Assertions.assertIssue(issues, index++, "ballerina/crypto:2", "argon_inline_named_arg.bal", + 20, 20, Source.BUILT_IN); + Assertions.assertIssue(issues, index++, "ballerina/crypto:2", "argon_inline_pos_arg.bal", + 20, 20, Source.BUILT_IN); + Assertions.assertIssue(issues, index++, "ballerina/crypto:2", "argon_mod_var_named_arg.bal", + 24, 24, Source.BUILT_IN); + Assertions.assertIssue(issues, index++, "ballerina/crypto:2", "argon_mod_var_pos_arg.bal", + 24, 24, Source.BUILT_IN); + Assertions.assertIssue(issues, index++, "ballerina/crypto:2", "bcrypt_func_var_named_arg.bal", + 21, 21, Source.BUILT_IN); + Assertions.assertIssue(issues, index++, "ballerina/crypto:2", "bcrypt_func_var_pos_arg.bal", + 21, 21, Source.BUILT_IN); + Assertions.assertIssue(issues, index++, "ballerina/crypto:2", "bcrypt_inline_named_arg.bal", + 20, 20, Source.BUILT_IN); + Assertions.assertIssue(issues, index++, "ballerina/crypto:2", "bcrypt_inline_pos_arg.bal", + 20, 20, Source.BUILT_IN); + Assertions.assertIssue(issues, index++, "ballerina/crypto:2", "bcrypt_mod_var_named_arg.bal", + 22, 22, Source.BUILT_IN); + Assertions.assertIssue(issues, index, "ballerina/crypto:2", "bcrypt_mod_var_pos_arg.bal", + 22, 22, Source.BUILT_IN); + break; + case AVOID_REUSING_COUNTER_MODE_VECTORS: + 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, index++, "ballerina/crypto:3", "func_var_pos_arg.bal", + 22, 22, Source.BUILT_IN); + Assertions.assertIssue(issues, index++, "ballerina/crypto:3", "inline_named_arg.bal", + 21, 21, Source.BUILT_IN); + Assertions.assertIssue(issues, index++, "ballerina/crypto:3", "inline_pos_arg.bal", + 21, 21, Source.BUILT_IN); + Assertions.assertIssue(issues, index++, "ballerina/crypto:3", "mod_var_named_arg.bal", + 23, 23, Source.BUILT_IN); + Assertions.assertIssue(issues, index, "ballerina/crypto:3", "mod_var_pos_arg.bal", + 23, 23, Source.BUILT_IN); + break; + default: + Assert.fail("Unhandled rule in validateIssues: " + rule); + break; + } + } + + private void validateOutput(ByteArrayOutputStream console, String targetPackageName) throws IOException { + String output = console.toString(UTF_8); + String jsonOutput = extractJson(output); + String expectedOutput = Files.readString(EXPECTED_OUTPUT_DIRECTORY.resolve(targetPackageName + ".json")); + assertJsonEqual(jsonOutput, expectedOutput); + } + + private static ProjectEnvironmentBuilder getEnvironmentBuilder() { + Environment environment = EnvironmentBuilder.getBuilder().setBallerinaHome(DISTRIBUTION_PATH).build(); + return ProjectEnvironmentBuilder.getBuilder(environment); + } + + private String extractJson(String consoleOutput) { + int startIndex = consoleOutput.indexOf("["); + int endIndex = consoleOutput.lastIndexOf("]"); + if (startIndex == -1 || endIndex == -1) { + return ""; + } + return consoleOutput.substring(startIndex, endIndex + 1); + } + + private void assertJsonEqual(String actual, String expected) { + Assert.assertEquals(normalizeString(actual), normalizeString(expected)); + } + + private static String normalizeString(String json) { + try { + ObjectMapper mapper = new ObjectMapper().configure(SerializationFeature.ORDER_MAP_ENTRIES_BY_KEYS, true); + JsonNode node = mapper.readTree(json); + String normalizedJson = mapper.writeValueAsString(node) + .replaceAll(":\".*" + MODULE_BALLERINA_CRYPTO, ":\"" + MODULE_BALLERINA_CRYPTO); + return isWindows() ? normalizedJson.replace("/", "\\\\") : normalizedJson; + } catch (Exception ignore) { + return json; + } + } + + private static boolean isWindows() { + return System.getProperty("os.name").toLowerCase(Locale.ENGLISH).startsWith("windows"); + } +} diff --git a/compiler-plugin-tests/src/test/resources/static_code_analyzer/ballerina_packages/rule1/Ballerina.toml b/compiler-plugin-tests/src/test/resources/static_code_analyzer/ballerina_packages/rule1/Ballerina.toml new file mode 100644 index 00000000..e6736726 --- /dev/null +++ b/compiler-plugin-tests/src/test/resources/static_code_analyzer/ballerina_packages/rule1/Ballerina.toml @@ -0,0 +1,8 @@ +[package] +org = "wso2" +name = "rule1" +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/rule1/aes_cbc.bal b/compiler-plugin-tests/src/test/resources/static_code_analyzer/ballerina_packages/rule1/aes_cbc.bal new file mode 100644 index 00000000..cd9e17ef --- /dev/null +++ b/compiler-plugin-tests/src/test/resources/static_code_analyzer/ballerina_packages/rule1/aes_cbc.bal @@ -0,0 +1,32 @@ +// 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 ballerina/random; + +public function aesCbc() returns error? { + string dataString = "Hello Ballerina!"; + byte[] data = dataString.toBytes(); + byte[16] key = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]; + foreach int i in 0 ... 15 { + key[i] = (check random:createIntInRange(0, 255)); + } + byte[16] initialVector = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]; + foreach int i in 0 ... 15 { + initialVector[i] = (check random:createIntInRange(0, 255)); + } + byte[] _ = check crypto:encryptAesCbc(data, key, initialVector); +} 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 new file mode 100644 index 00000000..60c1b325 --- /dev/null +++ b/compiler-plugin-tests/src/test/resources/static_code_analyzer/ballerina_packages/rule1/aes_cbc_as_import.bal @@ -0,0 +1,32 @@ +// 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 as c; +import ballerina/random; + +public function aesCbcAsImport() returns error? { + string dataString = "Hello Ballerina!"; + byte[] data = dataString.toBytes(); + byte[16] key = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]; + foreach int i in 0 ... 15 { + key[i] = (check random:createIntInRange(0, 255)); + } + byte[16] initialVector = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]; + foreach int i in 0 ... 15 { + initialVector[i] = (check random:createIntInRange(0, 255)); + } + byte[] _ = check c:encryptAesCbc(data, key, initialVector); +} 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 new file mode 100644 index 00000000..632eca1d --- /dev/null +++ b/compiler-plugin-tests/src/test/resources/static_code_analyzer/ballerina_packages/rule1/aes_ecb.bal @@ -0,0 +1,28 @@ +// 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 ballerina/random; + +public function aesEcb() returns error? { + string dataString = "Hello Ballerina!"; + byte[] data = dataString.toBytes(); + byte[16] key = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]; + foreach int i in 0 ... 15 { + key[i] = (check random:createIntInRange(0, 255)); + } + byte[] _ = check crypto:encryptAesEcb(data, key); +} 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 new file mode 100644 index 00000000..d70eb0d0 --- /dev/null +++ b/compiler-plugin-tests/src/test/resources/static_code_analyzer/ballerina_packages/rule1/aes_ecb_as_import.bal @@ -0,0 +1,28 @@ +// 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 as c; +import ballerina/random; + +public function aesEcbAsImport() returns error? { + string dataString = "Hello Ballerina!"; + byte[] data = dataString.toBytes(); + byte[16] key = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]; + foreach int i in 0 ... 15 { + key[i] = (check random:createIntInRange(0, 255)); + } + byte[] _ = check c:encryptAesEcb(data, key); +} 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/Ballerina.toml b/compiler-plugin-tests/src/test/resources/static_code_analyzer/ballerina_packages/rule2/Ballerina.toml new file mode 100644 index 00000000..03f4ee8c --- /dev/null +++ b/compiler-plugin-tests/src/test/resources/static_code_analyzer/ballerina_packages/rule2/Ballerina.toml @@ -0,0 +1,8 @@ +[package] +org = "wso2" +name = "rule2" +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/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 new file mode 100644 index 00000000..60c58d92 --- /dev/null +++ b/compiler-plugin-tests/src/test/resources/static_code_analyzer/ballerina_packages/rule2/argon_func_var_named_arg.bal @@ -0,0 +1,25 @@ +// 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 ArgonFuncVarNamedArg() returns error? { + string password = "your-password"; + int iterations = 1; + int memory = 1024; + int parallelism = 0; + string _ = check crypto:hashArgon2(password, iterations = iterations, memory = memory, parallelism = parallelism); +} 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 new file mode 100644 index 00000000..5d8ff4d4 --- /dev/null +++ b/compiler-plugin-tests/src/test/resources/static_code_analyzer/ballerina_packages/rule2/argon_func_var_pos_arg.bal @@ -0,0 +1,25 @@ +// 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 ArgonFuncVarPosArg() returns error? { + string password = "your-password"; + int iterations = 1; + int memory = 1024; + int parallelism = 0; + string _ = check crypto:hashArgon2(password, iterations, memory, parallelism); +} 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 new file mode 100644 index 00000000..00772606 --- /dev/null +++ b/compiler-plugin-tests/src/test/resources/static_code_analyzer/ballerina_packages/rule2/argon_inline_named_arg.bal @@ -0,0 +1,22 @@ +// 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 ArgonNamedArg() returns error? { + string password = "your-password"; + string _ = check crypto:hashArgon2(password, iterations = 1, memory = 1024, parallelism = 0); +} 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 new file mode 100644 index 00000000..0c48ea4b --- /dev/null +++ b/compiler-plugin-tests/src/test/resources/static_code_analyzer/ballerina_packages/rule2/argon_inline_pos_arg.bal @@ -0,0 +1,22 @@ +// 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 ArgonPosArg() returns error? { + string password = "your-password"; + string _ = check crypto:hashArgon2(password, 1, 1024, 0); +} 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 new file mode 100644 index 00000000..81559feb --- /dev/null +++ b/compiler-plugin-tests/src/test/resources/static_code_analyzer/ballerina_packages/rule2/argon_mod_var_named_arg.bal @@ -0,0 +1,26 @@ +// 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; + +int iterationsModVarNamed = 1; +int memoryModVarNamed = 1024; +int parallelismModVarNamed = 0; + +public function ArgonModVarNamedArg() returns error? { + string password = "your-password"; + string _ = check crypto:hashArgon2(password, iterations = iterationsModVarNamed, memory = memoryModVarNamed, parallelism = parallelismModVarNamed); +} 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 new file mode 100644 index 00000000..46ce9fbd --- /dev/null +++ b/compiler-plugin-tests/src/test/resources/static_code_analyzer/ballerina_packages/rule2/argon_mod_var_pos_arg.bal @@ -0,0 +1,26 @@ +// 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; + +int iterationsModVarPos = 1; +int memoryModVarPos = 1024; +int parallelismModVarPos = 0; + +public function ArgonModVarPosArg() returns error? { + string password = "your-password"; + string _ = check crypto:hashArgon2(password, iterations = iterationsModVarPos, memory = memoryModVarPos, parallelism = parallelismModVarPos); +} 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 new file mode 100644 index 00000000..5f45e77f --- /dev/null +++ b/compiler-plugin-tests/src/test/resources/static_code_analyzer/ballerina_packages/rule2/bcrypt_func_var_named_arg.bal @@ -0,0 +1,23 @@ +// 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 bcryptFuncVarNamedArg() returns error? { + string password = "your-password"; + int workFactor = 9; + string _ = check crypto:hashBcrypt(password, workFactor = workFactor); +} 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 new file mode 100644 index 00000000..7801dd4a --- /dev/null +++ b/compiler-plugin-tests/src/test/resources/static_code_analyzer/ballerina_packages/rule2/bcrypt_func_var_pos_arg.bal @@ -0,0 +1,23 @@ +// 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 bcryptFuncVarPosArg() returns error? { + string password = "your-password"; + int workFactor = 9; + string _ = check crypto:hashBcrypt(password, workFactor); +} 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 new file mode 100644 index 00000000..cd08eb98 --- /dev/null +++ b/compiler-plugin-tests/src/test/resources/static_code_analyzer/ballerina_packages/rule2/bcrypt_inline_named_arg.bal @@ -0,0 +1,22 @@ +// 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 bcryptNamedArg() returns error? { + string password = "your-password"; + string _ = check crypto:hashBcrypt(password, workFactor = 9); +} 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 new file mode 100644 index 00000000..48bcfcca --- /dev/null +++ b/compiler-plugin-tests/src/test/resources/static_code_analyzer/ballerina_packages/rule2/bcrypt_inline_pos_arg.bal @@ -0,0 +1,22 @@ +// 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 bcryptPosArg() returns error? { + string password = "your-password"; + string _ = check crypto:hashBcrypt(password, 9); +} 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 new file mode 100644 index 00000000..65b39420 --- /dev/null +++ b/compiler-plugin-tests/src/test/resources/static_code_analyzer/ballerina_packages/rule2/bcrypt_mod_var_named_arg.bal @@ -0,0 +1,24 @@ +// 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; + +int workFactorModVarNamed = 9; + +public function bcryptModVarNamedArg() returns error? { + string password = "your-password"; + string _ = check crypto:hashBcrypt(password, workFactor = workFactorModVarNamed); +} 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 new file mode 100644 index 00000000..b7658b4c --- /dev/null +++ b/compiler-plugin-tests/src/test/resources/static_code_analyzer/ballerina_packages/rule2/bcrypt_mod_var_pos_arg.bal @@ -0,0 +1,24 @@ +// 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; + +int workFactorModVarPos = 9; + +public function bcryptModVarPosArg() returns error? { + string password = "your-password"; + string _ = check crypto:hashBcrypt(password, workFactorModVarPos); +} 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/rule3/Ballerina.toml b/compiler-plugin-tests/src/test/resources/static_code_analyzer/ballerina_packages/rule3/Ballerina.toml new file mode 100644 index 00000000..abb9de58 --- /dev/null +++ b/compiler-plugin-tests/src/test/resources/static_code_analyzer/ballerina_packages/rule3/Ballerina.toml @@ -0,0 +1,8 @@ +[package] +org = "wso2" +name = "rule3" +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/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 new file mode 100644 index 00000000..5085746e --- /dev/null +++ b/compiler-plugin-tests/src/test/resources/static_code_analyzer/ballerina_packages/rule3/func_var_named_arg.bal @@ -0,0 +1,24 @@ +// 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 funcVarNamedArg(string data) returns 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(); + byte[] _ = check crypto:encryptAesGcm(dataBytes, key, iv = initialVector); +} 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 new file mode 100644 index 00000000..67897b63 --- /dev/null +++ b/compiler-plugin-tests/src/test/resources/static_code_analyzer/ballerina_packages/rule3/func_var_pos_arg.bal @@ -0,0 +1,24 @@ +// 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 funcVarPosArg(string data) returns 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(); + byte[] _ = check crypto:encryptAesGcm(dataBytes, key, initialVector); +} 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 new file mode 100644 index 00000000..26ae881b --- /dev/null +++ b/compiler-plugin-tests/src/test/resources/static_code_analyzer/ballerina_packages/rule3/inline_named_arg.bal @@ -0,0 +1,23 @@ +// 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 inlineNamedArg(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(); + byte[] _ = check crypto:encryptAesGcm(dataBytes, key, iv = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16]); +} 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 new file mode 100644 index 00000000..ea7c66cc --- /dev/null +++ b/compiler-plugin-tests/src/test/resources/static_code_analyzer/ballerina_packages/rule3/inline_pos_arg.bal @@ -0,0 +1,23 @@ +// 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 inlinePosArg(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(); + byte[] _ = check crypto:encryptAesGcm(dataBytes, key, [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16]); +} 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 new file mode 100644 index 00000000..fc770977 --- /dev/null +++ b/compiler-plugin-tests/src/test/resources/static_code_analyzer/ballerina_packages/rule3/mod_var_named_arg.bal @@ -0,0 +1,25 @@ +// 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; + +byte[16] initialVectorModNamed = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16]; + +public function modVarNamedArg(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(); + byte[] _ = check crypto:encryptAesGcm(dataBytes, key, iv = initialVectorModNamed); +} 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 new file mode 100644 index 00000000..cdc7db2f --- /dev/null +++ b/compiler-plugin-tests/src/test/resources/static_code_analyzer/ballerina_packages/rule3/mod_var_pos_arg.bal @@ -0,0 +1,25 @@ +// 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; + +byte[16] initialVectorModPos = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16]; + +public function modVarPosArg(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(); + byte[] _ = check crypto:encryptAesGcm(dataBytes, key, initialVectorModPos); +} 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 new file mode 100644 index 00000000..b50cc84c --- /dev/null +++ b/compiler-plugin-tests/src/test/resources/static_code_analyzer/expected_output/rule1.json @@ -0,0 +1,282 @@ +[ + { + "location": { + "filePath": "aes_cbc.bal", + "startLine": 19, + "endLine": 19, + "startColumn": 0, + "endColumn": 6, + "startOffset": 694, + "length": 6 + }, + "rule": { + "id": "ballerina:3", + "numericId": 3, + "description": "Non isolated public function", + "ruleKind": "CODE_SMELL" + }, + "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", + "startLine": 19, + "endLine": 19, + "startColumn": 0, + "endColumn": 6, + "startOffset": 699, + "length": 6 + }, + "rule": { + "id": "ballerina:3", + "numericId": 3, + "description": "Non isolated public function", + "ruleKind": "CODE_SMELL" + }, + "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", + "startLine": 19, + "endLine": 19, + "startColumn": 0, + "endColumn": 6, + "startOffset": 694, + "length": 6 + }, + "rule": { + "id": "ballerina:3", + "numericId": 3, + "description": "Non isolated public function", + "ruleKind": "CODE_SMELL" + }, + "source": "BUILT_IN", + "fileName": "rule1/aes_ecb.bal", + "filePath": "module-ballerina-crypto/compiler-plugin-tests/src/test/resources/static_code_analyzer/ballerina_packages/rule1/aes_ecb.bal" + }, + { + "location": { + "filePath": "aes_ecb_as_import.bal", + "startLine": 19, + "endLine": 19, + "startColumn": 0, + "endColumn": 6, + "startOffset": 699, + "length": 6 + }, + "rule": { + "id": "ballerina:3", + "numericId": 3, + "description": "Non isolated public function", + "ruleKind": "CODE_SMELL" + }, + "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": "aes_cbc.bal", + "startLine": 30, + "endLine": 30, + "startColumn": 21, + "endColumn": 67, + "startOffset": 1203, + "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/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", + "startLine": 30, + "endLine": 30, + "startColumn": 21, + "endColumn": 62, + "startOffset": 1216, + "length": 41 + }, + "rule": { + "id": "ballerina/crypto:1", + "numericId": 1, + "description": "Avoid using insecure cipher modes or padding schemes", + "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", + "startLine": 26, + "endLine": 26, + "startColumn": 21, + "endColumn": 52, + "startOffset": 1012, + "length": 31 + }, + "rule": { + "id": "ballerina/crypto:1", + "numericId": 1, + "description": "Avoid using insecure cipher modes or padding schemes", + "ruleKind": "VULNERABILITY" + }, + "source": "BUILT_IN", + "fileName": "rule1/aes_ecb.bal", + "filePath": "module-ballerina-crypto/compiler-plugin-tests/src/test/resources/static_code_analyzer/ballerina_packages/rule1/aes_ecb.bal" + }, + { + "location": { + "filePath": "aes_ecb_as_import.bal", + "startLine": 26, + "endLine": 26, + "startColumn": 21, + "endColumn": 47, + "startOffset": 1025, + "length": 26 + }, + "rule": { + "id": "ballerina/crypto:1", + "numericId": 1, + "description": "Avoid using insecure cipher modes or padding schemes", + "ruleKind": "VULNERABILITY" + }, + "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 new file mode 100644 index 00000000..cf7d9b5b --- /dev/null +++ b/compiler-plugin-tests/src/test/resources/static_code_analyzer/expected_output/rule2.json @@ -0,0 +1,582 @@ +[ + { + "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", + "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/argon_func_var_named_arg.bal", + "filePath": "module-ballerina-crypto/compiler-plugin-tests/src/test/resources/static_code_analyzer/ballerina_packages/rule2/argon_func_var_named_arg.bal" + }, + { + "location": { + "filePath": "argon_func_var_pos_arg.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/argon_func_var_pos_arg.bal", + "filePath": "module-ballerina-crypto/compiler-plugin-tests/src/test/resources/static_code_analyzer/ballerina_packages/rule2/argon_func_var_pos_arg.bal" + }, + { + "location": { + "filePath": "argon_inline_named_arg.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/argon_inline_named_arg.bal", + "filePath": "module-ballerina-crypto/compiler-plugin-tests/src/test/resources/static_code_analyzer/ballerina_packages/rule2/argon_inline_named_arg.bal" + }, + { + "location": { + "filePath": "argon_inline_pos_arg.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/argon_inline_pos_arg.bal", + "filePath": "module-ballerina-crypto/compiler-plugin-tests/src/test/resources/static_code_analyzer/ballerina_packages/rule2/argon_inline_pos_arg.bal" + }, + { + "location": { + "filePath": "argon_mod_var_named_arg.bal", + "startLine": 22, + "endLine": 22, + "startColumn": 0, + "endColumn": 6, + "startOffset": 763, + "length": 6 + }, + "rule": { + "id": "ballerina:3", + "numericId": 3, + "description": "Non isolated public function", + "ruleKind": "CODE_SMELL" + }, + "source": "BUILT_IN", + "fileName": "rule2/argon_mod_var_named_arg.bal", + "filePath": "module-ballerina-crypto/compiler-plugin-tests/src/test/resources/static_code_analyzer/ballerina_packages/rule2/argon_mod_var_named_arg.bal" + }, + { + "location": { + "filePath": "argon_mod_var_pos_arg.bal", + "startLine": 22, + "endLine": 22, + "startColumn": 0, + "endColumn": 6, + "startOffset": 757, + "length": 6 + }, + "rule": { + "id": "ballerina:3", + "numericId": 3, + "description": "Non isolated public function", + "ruleKind": "CODE_SMELL" + }, + "source": "BUILT_IN", + "fileName": "rule2/argon_mod_var_pos_arg.bal", + "filePath": "module-ballerina-crypto/compiler-plugin-tests/src/test/resources/static_code_analyzer/ballerina_packages/rule2/argon_mod_var_pos_arg.bal" + }, + { + "location": { + "filePath": "bcrypt_func_var_named_arg.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/bcrypt_func_var_named_arg.bal", + "filePath": "module-ballerina-crypto/compiler-plugin-tests/src/test/resources/static_code_analyzer/ballerina_packages/rule2/bcrypt_func_var_named_arg.bal" + }, + { + "location": { + "filePath": "bcrypt_func_var_pos_arg.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/bcrypt_func_var_pos_arg.bal", + "filePath": "module-ballerina-crypto/compiler-plugin-tests/src/test/resources/static_code_analyzer/ballerina_packages/rule2/bcrypt_func_var_pos_arg.bal" + }, + { + "location": { + "filePath": "bcrypt_inline_named_arg.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/bcrypt_inline_named_arg.bal", + "filePath": "module-ballerina-crypto/compiler-plugin-tests/src/test/resources/static_code_analyzer/ballerina_packages/rule2/bcrypt_inline_named_arg.bal" + }, + { + "location": { + "filePath": "bcrypt_inline_pos_arg.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/bcrypt_inline_pos_arg.bal", + "filePath": "module-ballerina-crypto/compiler-plugin-tests/src/test/resources/static_code_analyzer/ballerina_packages/rule2/bcrypt_inline_pos_arg.bal" + }, + { + "location": { + "filePath": "bcrypt_mod_var_named_arg.bal", + "startLine": 20, + "endLine": 20, + "startColumn": 0, + "endColumn": 6, + "startOffset": 701, + "length": 6 + }, + "rule": { + "id": "ballerina:3", + "numericId": 3, + "description": "Non isolated public function", + "ruleKind": "CODE_SMELL" + }, + "source": "BUILT_IN", + "fileName": "rule2/bcrypt_mod_var_named_arg.bal", + "filePath": "module-ballerina-crypto/compiler-plugin-tests/src/test/resources/static_code_analyzer/ballerina_packages/rule2/bcrypt_mod_var_named_arg.bal" + }, + { + "location": { + "filePath": "bcrypt_mod_var_pos_arg.bal", + "startLine": 20, + "endLine": 20, + "startColumn": 0, + "endColumn": 6, + "startOffset": 699, + "length": 6 + }, + "rule": { + "id": "ballerina:3", + "numericId": 3, + "description": "Non isolated public function", + "ruleKind": "CODE_SMELL" + }, + "source": "BUILT_IN", + "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": "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", + "startLine": 23, + "endLine": 23, + "startColumn": 21, + "endColumn": 117, + "startOffset": 857, + "length": 96 + }, + "rule": { + "id": "ballerina/crypto:2", + "numericId": 2, + "description": "Avoid using fast hashing algorithms", + "ruleKind": "VULNERABILITY" + }, + "source": "BUILT_IN", + "fileName": "rule2/argon_func_var_named_arg.bal", + "filePath": "module-ballerina-crypto/compiler-plugin-tests/src/test/resources/static_code_analyzer/ballerina_packages/rule2/argon_func_var_named_arg.bal" + }, + { + "location": { + "filePath": "argon_func_var_pos_arg.bal", + "startLine": 23, + "endLine": 23, + "startColumn": 21, + "endColumn": 81, + "startOffset": 855, + "length": 60 + }, + "rule": { + "id": "ballerina/crypto:2", + "numericId": 2, + "description": "Avoid using fast hashing algorithms", + "ruleKind": "VULNERABILITY" + }, + "source": "BUILT_IN", + "fileName": "rule2/argon_func_var_pos_arg.bal", + "filePath": "module-ballerina-crypto/compiler-plugin-tests/src/test/resources/static_code_analyzer/ballerina_packages/rule2/argon_func_var_pos_arg.bal" + }, + { + "location": { + "filePath": "argon_inline_named_arg.bal", + "startLine": 20, + "endLine": 20, + "startColumn": 21, + "endColumn": 96, + "startOffset": 778, + "length": 75 + }, + "rule": { + "id": "ballerina/crypto:2", + "numericId": 2, + "description": "Avoid using fast hashing algorithms", + "ruleKind": "VULNERABILITY" + }, + "source": "BUILT_IN", + "fileName": "rule2/argon_inline_named_arg.bal", + "filePath": "module-ballerina-crypto/compiler-plugin-tests/src/test/resources/static_code_analyzer/ballerina_packages/rule2/argon_inline_named_arg.bal" + }, + { + "location": { + "filePath": "argon_inline_pos_arg.bal", + "startLine": 20, + "endLine": 20, + "startColumn": 21, + "endColumn": 60, + "startOffset": 776, + "length": 39 + }, + "rule": { + "id": "ballerina/crypto:2", + "numericId": 2, + "description": "Avoid using fast hashing algorithms", + "ruleKind": "VULNERABILITY" + }, + "source": "BUILT_IN", + "fileName": "rule2/argon_inline_pos_arg.bal", + "filePath": "module-ballerina-crypto/compiler-plugin-tests/src/test/resources/static_code_analyzer/ballerina_packages/rule2/argon_inline_pos_arg.bal" + }, + { + "location": { + "filePath": "argon_mod_var_named_arg.bal", + "startLine": 24, + "endLine": 24, + "startColumn": 21, + "endColumn": 150, + "startOffset": 878, + "length": 129 + }, + "rule": { + "id": "ballerina/crypto:2", + "numericId": 2, + "description": "Avoid using fast hashing algorithms", + "ruleKind": "VULNERABILITY" + }, + "source": "BUILT_IN", + "fileName": "rule2/argon_mod_var_named_arg.bal", + "filePath": "module-ballerina-crypto/compiler-plugin-tests/src/test/resources/static_code_analyzer/ballerina_packages/rule2/argon_mod_var_named_arg.bal" + }, + { + "location": { + "filePath": "argon_mod_var_pos_arg.bal", + "startLine": 24, + "endLine": 24, + "startColumn": 21, + "endColumn": 144, + "startOffset": 870, + "length": 123 + }, + "rule": { + "id": "ballerina/crypto:2", + "numericId": 2, + "description": "Avoid using fast hashing algorithms", + "ruleKind": "VULNERABILITY" + }, + "source": "BUILT_IN", + "fileName": "rule2/argon_mod_var_pos_arg.bal", + "filePath": "module-ballerina-crypto/compiler-plugin-tests/src/test/resources/static_code_analyzer/ballerina_packages/rule2/argon_mod_var_pos_arg.bal" + }, + { + "location": { + "filePath": "bcrypt_func_var_named_arg.bal", + "startLine": 21, + "endLine": 21, + "startColumn": 21, + "endColumn": 73, + "startOffset": 810, + "length": 52 + }, + "rule": { + "id": "ballerina/crypto:2", + "numericId": 2, + "description": "Avoid using fast hashing algorithms", + "ruleKind": "VULNERABILITY" + }, + "source": "BUILT_IN", + "fileName": "rule2/bcrypt_func_var_named_arg.bal", + "filePath": "module-ballerina-crypto/compiler-plugin-tests/src/test/resources/static_code_analyzer/ballerina_packages/rule2/bcrypt_func_var_named_arg.bal" + }, + { + "location": { + "filePath": "bcrypt_func_var_pos_arg.bal", + "startLine": 21, + "endLine": 21, + "startColumn": 21, + "endColumn": 60, + "startOffset": 808, + "length": 39 + }, + "rule": { + "id": "ballerina/crypto:2", + "numericId": 2, + "description": "Avoid using fast hashing algorithms", + "ruleKind": "VULNERABILITY" + }, + "source": "BUILT_IN", + "fileName": "rule2/bcrypt_func_var_pos_arg.bal", + "filePath": "module-ballerina-crypto/compiler-plugin-tests/src/test/resources/static_code_analyzer/ballerina_packages/rule2/bcrypt_func_var_pos_arg.bal" + }, + { + "location": { + "filePath": "bcrypt_inline_named_arg.bal", + "startLine": 20, + "endLine": 20, + "startColumn": 21, + "endColumn": 64, + "startOffset": 779, + "length": 43 + }, + "rule": { + "id": "ballerina/crypto:2", + "numericId": 2, + "description": "Avoid using fast hashing algorithms", + "ruleKind": "VULNERABILITY" + }, + "source": "BUILT_IN", + "fileName": "rule2/bcrypt_inline_named_arg.bal", + "filePath": "module-ballerina-crypto/compiler-plugin-tests/src/test/resources/static_code_analyzer/ballerina_packages/rule2/bcrypt_inline_named_arg.bal" + }, + { + "location": { + "filePath": "bcrypt_inline_pos_arg.bal", + "startLine": 20, + "endLine": 20, + "startColumn": 21, + "endColumn": 51, + "startOffset": 777, + "length": 30 + }, + "rule": { + "id": "ballerina/crypto:2", + "numericId": 2, + "description": "Avoid using fast hashing algorithms", + "ruleKind": "VULNERABILITY" + }, + "source": "BUILT_IN", + "fileName": "rule2/bcrypt_inline_pos_arg.bal", + "filePath": "module-ballerina-crypto/compiler-plugin-tests/src/test/resources/static_code_analyzer/ballerina_packages/rule2/bcrypt_inline_pos_arg.bal" + }, + { + "location": { + "filePath": "bcrypt_mod_var_named_arg.bal", + "startLine": 22, + "endLine": 22, + "startColumn": 21, + "endColumn": 84, + "startOffset": 817, + "length": 63 + }, + "rule": { + "id": "ballerina/crypto:2", + "numericId": 2, + "description": "Avoid using fast hashing algorithms", + "ruleKind": "VULNERABILITY" + }, + "source": "BUILT_IN", + "fileName": "rule2/bcrypt_mod_var_named_arg.bal", + "filePath": "module-ballerina-crypto/compiler-plugin-tests/src/test/resources/static_code_analyzer/ballerina_packages/rule2/bcrypt_mod_var_named_arg.bal" + }, + { + "location": { + "filePath": "bcrypt_mod_var_pos_arg.bal", + "startLine": 22, + "endLine": 22, + "startColumn": 21, + "endColumn": 69, + "startOffset": 813, + "length": 48 + }, + "rule": { + "id": "ballerina/crypto:2", + "numericId": 2, + "description": "Avoid using fast hashing algorithms", + "ruleKind": "VULNERABILITY" + }, + "source": "BUILT_IN", + "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" + } +] 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 new file mode 100644 index 00000000..fb101468 --- /dev/null +++ b/compiler-plugin-tests/src/test/resources/static_code_analyzer/expected_output/rule3.json @@ -0,0 +1,402 @@ +[ + { + "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", + "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": "rule3/func_var_named_arg.bal", + "filePath": "module-ballerina-crypto/compiler-plugin-tests/src/test/resources/static_code_analyzer/ballerina_packages/rule3/func_var_named_arg.bal" + }, + { + "location": { + "filePath": "func_var_pos_arg.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": "rule3/func_var_pos_arg.bal", + "filePath": "module-ballerina-crypto/compiler-plugin-tests/src/test/resources/static_code_analyzer/ballerina_packages/rule3/func_var_pos_arg.bal" + }, + { + "location": { + "filePath": "inline_named_arg.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": "rule3/inline_named_arg.bal", + "filePath": "module-ballerina-crypto/compiler-plugin-tests/src/test/resources/static_code_analyzer/ballerina_packages/rule3/inline_named_arg.bal" + }, + { + "location": { + "filePath": "inline_pos_arg.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": "rule3/inline_pos_arg.bal", + "filePath": "module-ballerina-crypto/compiler-plugin-tests/src/test/resources/static_code_analyzer/ballerina_packages/rule3/inline_pos_arg.bal" + }, + { + "location": { + "filePath": "mod_var_named_arg.bal", + "startLine": 20, + "endLine": 20, + "startColumn": 0, + "endColumn": 6, + "startOffset": 760, + "length": 6 + }, + "rule": { + "id": "ballerina:3", + "numericId": 3, + "description": "Non isolated public function", + "ruleKind": "CODE_SMELL" + }, + "source": "BUILT_IN", + "fileName": "rule3/mod_var_named_arg.bal", + "filePath": "module-ballerina-crypto/compiler-plugin-tests/src/test/resources/static_code_analyzer/ballerina_packages/rule3/mod_var_named_arg.bal" + }, + { + "location": { + "filePath": "mod_var_pos_arg.bal", + "startLine": 20, + "endLine": 20, + "startColumn": 0, + "endColumn": 6, + "startOffset": 758, + "length": 6 + }, + "rule": { + "id": "ballerina:3", + "numericId": 3, + "description": "Non isolated public function", + "ruleKind": "CODE_SMELL" + }, + "source": "BUILT_IN", + "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", + "startLine": 22, + "endLine": 22, + "startColumn": 21, + "endColumn": 77, + "startOffset": 953, + "length": 56 + }, + "rule": { + "id": "ballerina/crypto:3", + "numericId": 3, + "description": "Avoid reusing counter mode initialization vectors", + "ruleKind": "VULNERABILITY" + }, + "source": "BUILT_IN", + "fileName": "rule3/func_var_named_arg.bal", + "filePath": "module-ballerina-crypto/compiler-plugin-tests/src/test/resources/static_code_analyzer/ballerina_packages/rule3/func_var_named_arg.bal" + }, + { + "location": { + "filePath": "func_var_pos_arg.bal", + "startLine": 22, + "endLine": 22, + "startColumn": 21, + "endColumn": 72, + "startOffset": 951, + "length": 51 + }, + "rule": { + "id": "ballerina/crypto:3", + "numericId": 3, + "description": "Avoid reusing counter mode initialization vectors", + "ruleKind": "VULNERABILITY" + }, + "source": "BUILT_IN", + "fileName": "rule3/func_var_pos_arg.bal", + "filePath": "module-ballerina-crypto/compiler-plugin-tests/src/test/resources/static_code_analyzer/ballerina_packages/rule3/func_var_pos_arg.bal" + }, + { + "location": { + "filePath": "inline_named_arg.bal", + "startLine": 21, + "endLine": 21, + "startColumn": 21, + "endColumn": 119, + "startOffset": 866, + "length": 98 + }, + "rule": { + "id": "ballerina/crypto:3", + "numericId": 3, + "description": "Avoid reusing counter mode initialization vectors", + "ruleKind": "VULNERABILITY" + }, + "source": "BUILT_IN", + "fileName": "rule3/inline_named_arg.bal", + "filePath": "module-ballerina-crypto/compiler-plugin-tests/src/test/resources/static_code_analyzer/ballerina_packages/rule3/inline_named_arg.bal" + }, + { + "location": { + "filePath": "inline_pos_arg.bal", + "startLine": 21, + "endLine": 21, + "startColumn": 21, + "endColumn": 114, + "startOffset": 864, + "length": 93 + }, + "rule": { + "id": "ballerina/crypto:3", + "numericId": 3, + "description": "Avoid reusing counter mode initialization vectors", + "ruleKind": "VULNERABILITY" + }, + "source": "BUILT_IN", + "fileName": "rule3/inline_pos_arg.bal", + "filePath": "module-ballerina-crypto/compiler-plugin-tests/src/test/resources/static_code_analyzer/ballerina_packages/rule3/inline_pos_arg.bal" + }, + { + "location": { + "filePath": "mod_var_named_arg.bal", + "startLine": 23, + "endLine": 23, + "startColumn": 21, + "endColumn": 85, + "startOffset": 957, + "length": 64 + }, + "rule": { + "id": "ballerina/crypto:3", + "numericId": 3, + "description": "Avoid reusing counter mode initialization vectors", + "ruleKind": "VULNERABILITY" + }, + "source": "BUILT_IN", + "fileName": "rule3/mod_var_named_arg.bal", + "filePath": "module-ballerina-crypto/compiler-plugin-tests/src/test/resources/static_code_analyzer/ballerina_packages/rule3/mod_var_named_arg.bal" + }, + { + "location": { + "filePath": "mod_var_pos_arg.bal", + "startLine": 23, + "endLine": 23, + "startColumn": 21, + "endColumn": 78, + "startOffset": 953, + "length": 57 + }, + "rule": { + "id": "ballerina/crypto:3", + "numericId": 3, + "description": "Avoid reusing counter mode initialization vectors", + "ruleKind": "VULNERABILITY" + }, + "source": "BUILT_IN", + "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" + } +] diff --git a/compiler-plugin-tests/src/test/resources/testng.xml b/compiler-plugin-tests/src/test/resources/testng.xml new file mode 100644 index 00000000..28d782e6 --- /dev/null +++ b/compiler-plugin-tests/src/test/resources/testng.xml @@ -0,0 +1,29 @@ + + + + + + + + + + + + + diff --git a/compiler-plugin/build.gradle b/compiler-plugin/build.gradle new file mode 100644 index 00000000..3a8c41cb --- /dev/null +++ b/compiler-plugin/build.gradle @@ -0,0 +1,78 @@ +/* + * 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. + */ + +plugins { + id 'java' + id 'checkstyle' + id 'com.github.spotbugs' +} + +description = 'Ballerina - Crypto Compiler Plugin' + + +dependencies { + checkstyle project(':checkstyle') + checkstyle "com.puppycrawl.tools:checkstyle:${checkstylePluginVersion}" + + implementation group: 'org.ballerinalang', name: 'ballerina-lang', version: "${ballerinaLangVersion}" + implementation group: 'org.ballerinalang', name: 'ballerina-tools-api', version: "${ballerinaLangVersion}" + implementation group: 'org.ballerinalang', name: 'ballerina-parser', version: "${ballerinaLangVersion}" + implementation group: 'io.ballerina.scan', name: 'scan-command', version: "${balScanVersion}" + implementation project(":crypto-native") +} + +def excludePattern = '**/module-info.java' +tasks.withType(Checkstyle).configureEach { + exclude excludePattern +} + +checkstyle { + toolVersion "${project.checkstylePluginVersion}" + configFile rootProject.file("build-config/checkstyle/build/checkstyle.xml") + configProperties = ["suppressionFile": file("${rootDir}/build-config/checkstyle/build/suppressions.xml")] +} + +checkstyleMain.dependsOn(":checkstyle:downloadCheckstyleRuleFiles") + +spotbugsMain { + def classLoader = plugins["com.github.spotbugs"].class.classLoader + def SpotBugsConfidence = classLoader.findLoadedClass("com.github.spotbugs.snom.Confidence") + def SpotBugsEffort = classLoader.findLoadedClass("com.github.spotbugs.snom.Effort") + effort = SpotBugsEffort.MAX + reportLevel = SpotBugsConfidence.LOW + reportsDir = file("$project.buildDir/reports/spotbugs") + reports { + html.enabled true + text.enabled = true + } + def excludeFile = file("${rootDir}/spotbugs-exclude.xml") + if (excludeFile.exists()) { + excludeFilter = excludeFile + } +} + +compileJava { + doFirst { + options.compilerArgs = [ + '--module-path', classpath.asPath, + ] + classpath = files() + } +} + +build.dependsOn ":crypto-native:build" diff --git a/compiler-plugin/src/main/java/io/ballerina/stdlib/crypto/compiler/CryptoCompilerPlugin.java b/compiler-plugin/src/main/java/io/ballerina/stdlib/crypto/compiler/CryptoCompilerPlugin.java new file mode 100644 index 00000000..5a425670 --- /dev/null +++ b/compiler-plugin/src/main/java/io/ballerina/stdlib/crypto/compiler/CryptoCompilerPlugin.java @@ -0,0 +1,41 @@ +/* + * 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; + +import io.ballerina.projects.plugins.CompilerPlugin; +import io.ballerina.projects.plugins.CompilerPluginContext; +import io.ballerina.scan.ScannerContext; +import io.ballerina.stdlib.crypto.compiler.staticcodeanalyzer.CryptoCodeAnalyzer; + +/** + * Crypto compiler plugin. + * + * @since 2.9.1 + */ +public class CryptoCompilerPlugin extends CompilerPlugin { + private static final String SCANNER_CONTEXT = "ScannerContext"; + + @Override + public void init(CompilerPluginContext context) { + Object object = context.userData().get(SCANNER_CONTEXT); + if (object instanceof ScannerContext scannerContext) { + context.addCodeAnalyzer(new CryptoCodeAnalyzer(scannerContext.getReporter())); + } + } +} 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 new file mode 100644 index 00000000..f197e7b3 --- /dev/null +++ b/compiler-plugin/src/main/java/io/ballerina/stdlib/crypto/compiler/staticcodeanalyzer/CryptoAnalyzerUtils.java @@ -0,0 +1,369 @@ +/* + * 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.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.FunctionCallExpressionNode; +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.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; +import io.ballerina.projects.Document; +import io.ballerina.projects.DocumentId; +import io.ballerina.projects.Module; + +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Optional; + +/** + * Utility class containing helper methods for crypto cipher algorithm analysis. + * This class provides common functionality for checking weak parameters and + * variable analysis. + */ +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. + * + * @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 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(); + } + ModuleID moduleId = (functionCallSymbolOptional.get()).getModule().get().id(); + if (BALLERINA_ORG.equals(moduleId.orgName()) && CRYPTO.equals(moduleId.packageName())) { + return Optional.of(functionSymbol); + } + return Optional.empty(); + } + + /** + * 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); + } + + /** + * Unescape the given identifier name by removing leading escape quote and backslashes. + * + * @param identifierName The identifier name to unescape + * @return The unescaped identifier name + */ + public static String unescapeIdentifier(String identifierName) { + String result = identifierName; + if (result.startsWith("'")) { + result = result.substring(1); + } + return result.replace("\\\\", ""); + } + + + /** + * Maps parameter names to their corresponding argument expressions in a function call. + * + * @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 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); + } + // Not handling RestArgumentNode at the moment as crypto functions do not have rest parameters + } + return paramExpressions; + } + + /** + * Retrieves the StatementNode that contains the given FunctionCallExpressionNode. + * + * @param functionCall the function call expression node + * @return an Optional containing the StatementNode if found, otherwise empty + */ + 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 Optional.empty(); + } + + /** + * Recursively retrieves the nearest parent block node (FunctionBodyBlockNode or BlockStatementNode) + * of the given node. + * + * @param node the starting node + * @return an Optional containing the parent block node if found, otherwise empty + */ + 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); + }; + } + + /** + * Recursively retrieves the ModulePartNode that contains the given node. + * + * @param node the starting node + * @return an Optional containing the ModulePartNode if found, otherwise empty + */ + 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); + }; + } + + /** + * Retrieves a map of module-level variable names to their initializer expressions + * from the given ModulePartNode. + * + * @param modulePartNode the module part node + * @return a map where keys are variable names and values are their initializer expressions + */ + 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; + } + String varName = captureBindingPattern.variableName().text(); + varName = unescapeIdentifier(varName); + ExpressionNode initializer = variableDeclarationNode.initializer().get(); + varExpressions.put(varName, initializer); + } + } + return varExpressions; + } + + /** + * 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 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); + } + + /** + * Checks if the given statement node is a block statement or not. + * + * @param statement the statement node to check + * @return true if the statement is a block statement, false otherwise + */ + 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); + } + + /** + * Retrieves the string value of a parameter from the function context. + * + * @param key the parameter name + * @param context the function context + * @return an Optional containing the string value if found, otherwise empty + */ + public static Optional getStringValue(String key, FunctionContext context) { + Optional valueExprOpt = context.getParamExpression(key); + if (valueExprOpt.isEmpty()) { + return Optional.empty(); + } + + 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 && + 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 SimpleNameReferenceNode simpleNameRef) { + String varName = unescapeIdentifier(simpleNameRef.name().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/CryptoCipherAlgorithmAnalyzer.java b/compiler-plugin/src/main/java/io/ballerina/stdlib/crypto/compiler/staticcodeanalyzer/CryptoCipherAlgorithmAnalyzer.java new file mode 100644 index 00000000..b68fdcf2 --- /dev/null +++ b/compiler-plugin/src/main/java/io/ballerina/stdlib/crypto/compiler/staticcodeanalyzer/CryptoCipherAlgorithmAnalyzer.java @@ -0,0 +1,71 @@ +/* + * 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.syntax.tree.FunctionCallExpressionNode; +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.Optional; + +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 final CryptoFunctionRulesEngine rulesEngine; + + public CryptoCipherAlgorithmAnalyzer(Reporter reporter) { + this.reporter = reporter; + this.rulesEngine = new CryptoFunctionRulesEngine(); + } + + /** + * Analyzes the syntax tree for function calls to crypto module functions. + * + * @param context the syntax node analysis context + */ + @Override + public void perform(SyntaxNodeAnalysisContext context) { + FunctionCallExpressionNode functionCall = (FunctionCallExpressionNode) context.node(); + 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; + } + + Optional functionSymbolOpt = getCryptoFunctionSymbol(functionCall, semanticModel); + if (functionSymbolOpt.isEmpty()) { + return; + } + + 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/CryptoCodeAnalyzer.java b/compiler-plugin/src/main/java/io/ballerina/stdlib/crypto/compiler/staticcodeanalyzer/CryptoCodeAnalyzer.java new file mode 100644 index 00000000..b9378a7b --- /dev/null +++ b/compiler-plugin/src/main/java/io/ballerina/stdlib/crypto/compiler/staticcodeanalyzer/CryptoCodeAnalyzer.java @@ -0,0 +1,38 @@ +/* + * 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.syntax.tree.SyntaxKind; +import io.ballerina.projects.plugins.CodeAnalysisContext; +import io.ballerina.projects.plugins.CodeAnalyzer; +import io.ballerina.scan.Reporter; + +public class CryptoCodeAnalyzer extends CodeAnalyzer { + private final Reporter reporter; + + public CryptoCodeAnalyzer(Reporter reporter) { + this.reporter = reporter; + } + + @Override + public void init(CodeAnalysisContext codeAnalysisContext) { + codeAnalysisContext.addSyntaxNodeAnalysisTask(new CryptoCipherAlgorithmAnalyzer(reporter), + SyntaxKind.FUNCTION_CALL); + } +} 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 new file mode 100644 index 00000000..60f0d899 --- /dev/null +++ b/compiler-plugin/src/main/java/io/ballerina/stdlib/crypto/compiler/staticcodeanalyzer/CryptoRule.java @@ -0,0 +1,53 @@ +/* + * 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.scan.Rule; + +import static io.ballerina.scan.RuleKind.VULNERABILITY; +import static io.ballerina.stdlib.crypto.compiler.staticcodeanalyzer.RuleFactory.createRule; + +public enum CryptoRule { + AVOID_WEAK_CIPHER_ALGORITHMS(createRule(1, + "Avoid using insecure cipher modes or padding schemes", VULNERABILITY)), + 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)); + + private final Rule rule; + + CryptoRule(Rule rule) { + this.rule = rule; + } + + public int getId() { + return this.rule.numericId(); + } + + public String getDescription() { + return this.rule.description(); + } + + @Override + public String toString() { + return "{\"id\":" + this.getId() + ", \"kind\":\"" + this.rule.kind() + "\"," + + " \"description\" : \"" + this.rule.description() + "\"}"; + } +} 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..a6c7d77e --- /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.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; +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 + collectVariableExpressionsUntilStatement(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 + */ + public 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/RuleFactory.java b/compiler-plugin/src/main/java/io/ballerina/stdlib/crypto/compiler/staticcodeanalyzer/RuleFactory.java new file mode 100644 index 00000000..fba2ea03 --- /dev/null +++ b/compiler-plugin/src/main/java/io/ballerina/stdlib/crypto/compiler/staticcodeanalyzer/RuleFactory.java @@ -0,0 +1,35 @@ +/* + * 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.scan.Rule; +import io.ballerina.scan.RuleKind; + +/** + * {@code RuleFactory} contains the logic to create a {@link Rule}. + */ +public class RuleFactory { + + private RuleFactory() { + } + + public static Rule createRule(int id, String description, RuleKind kind) { + return new RuleImpl(id, description, kind); + } +} diff --git a/compiler-plugin/src/main/java/io/ballerina/stdlib/crypto/compiler/staticcodeanalyzer/RuleImpl.java b/compiler-plugin/src/main/java/io/ballerina/stdlib/crypto/compiler/staticcodeanalyzer/RuleImpl.java new file mode 100644 index 00000000..80a87b2b --- /dev/null +++ b/compiler-plugin/src/main/java/io/ballerina/stdlib/crypto/compiler/staticcodeanalyzer/RuleImpl.java @@ -0,0 +1,54 @@ +/* + * 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.scan.Rule; +import io.ballerina.scan.RuleKind; + +public class RuleImpl implements Rule { + private final int id; + private final String description; + private final RuleKind kind; + + RuleImpl(int id, String description, RuleKind kind) { + this.id = id; + this.description = description; + this.kind = kind; + } + + @Override + public String id() { + return Integer.toString(this.id); + } + + @Override + public int numericId() { + return this.id; + } + + @Override + public String description() { + return this.description; + } + + @Override + public RuleKind kind() { + return this.kind; + } +} 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..eedd89b6 --- /dev/null +++ b/compiler-plugin/src/main/java/io/ballerina/stdlib/crypto/compiler/staticcodeanalyzer/functionrules/AvoidFastHashAlgorithmsRule.java @@ -0,0 +1,125 @@ +/* + * 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.api.values.ConstantValue; +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.CryptoRule.AVOID_FAST_HASH_ALGORITHMS; + +/** + * Rule to avoid fast hash algorithms. + * Analyzes the usage of bcrypt, and argon2 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 WORK_FACTOR = "workFactor"; + public static final String ITERATIONS = "iterations"; + public static final String MEMORY = "memory"; + + 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; + + @Override + public void analyze(FunctionContext context) { + if (isBCryptWithLowWorkFactor(context) || isArgon2WithWeakParams(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); + } + + 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 + // 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); + } + + 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 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 && + 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 new file mode 100644 index 00000000..ce323e0c --- /dev/null +++ b/compiler-plugin/src/main/java/io/ballerina/stdlib/crypto/compiler/staticcodeanalyzer/functionrules/AvoidReusingCounterModeVectorsRule.java @@ -0,0 +1,118 @@ +/* + * 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.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; +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.CryptoAnalyzerUtils.unescapeIdentifier; +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)) { + 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, 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 referring to a constant + if (ivExpression instanceof MethodCallExpressionNode methodCallExpression) { + ExpressionNode expression = methodCallExpression.expression(); + if (!isMethodCallOnConstantExpr(expression, context)) { + return false; + } + NameReferenceNode nameReferenceNode = methodCallExpression.methodName(); + if (nameReferenceNode instanceof SimpleNameReferenceNode simpleNameRef) { + return simpleNameRef.name().text().equals(TO_BYTES_METHOD); + } + } + + 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/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/java/module-info.java b/compiler-plugin/src/main/java/module-info.java new file mode 100644 index 00000000..0a406a12 --- /dev/null +++ b/compiler-plugin/src/main/java/module-info.java @@ -0,0 +1,24 @@ +/* + * 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. + */ + +module io.ballerina.stdlib.crypto.compiler { + requires io.ballerina.lang; + requires io.ballerina.tools.api; + requires io.ballerina.parser; + requires io.ballerina.scan; +} diff --git a/compiler-plugin/src/main/resources/rules.json b/compiler-plugin/src/main/resources/rules.json new file mode 100644 index 00000000..567d9129 --- /dev/null +++ b/compiler-plugin/src/main/resources/rules.json @@ -0,0 +1,17 @@ +[ + { + "id": 1, + "kind": "VULNERABILITY", + "description": "Avoid using insecure cipher modes or padding schemes" + }, + { + "id": 2, + "kind": "VULNERABILITY", + "description": "Avoid using fast hashing algorithms" + }, + { + "id": 3, + "kind": "VULNERABILITY", + "description": "Avoid reusing counter mode initialization vectors" + } +] diff --git a/docs/spec/spec.md b/docs/spec/spec.md index 4fef99eb..6cd957ba 100644 --- a/docs/spec/spec.md +++ b/docs/spec/spec.md @@ -3,8 +3,8 @@ _Owners_: @shafreenAnfar @bhashinee _Reviewers_: @shafreenAnfar _Created_: 2022/08/23 -_Updated_: 2025/01/20 -_Edition_: Swan Lake +_Updated_: 2025/10/17 +_Edition_: Swan Lake ## Introduction @@ -17,172 +17,183 @@ 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.1 [BCrypt](#101-bcrypt) + - 10.2 [Argon2](#102-argon2) +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. @@ -194,7 +205,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. @@ -206,7 +217,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. @@ -218,7 +229,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. @@ -230,7 +241,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. @@ -242,11 +253,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. @@ -258,7 +269,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. @@ -267,7 +278,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. @@ -276,7 +287,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. @@ -288,7 +299,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. @@ -297,7 +308,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. @@ -306,7 +317,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. @@ -318,7 +329,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. @@ -327,7 +338,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. @@ -339,7 +350,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. @@ -348,7 +359,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. @@ -361,7 +372,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. @@ -373,7 +384,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. @@ -382,7 +393,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. @@ -394,7 +405,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. @@ -403,7 +414,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. @@ -415,7 +426,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. @@ -424,7 +435,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. @@ -436,7 +447,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. @@ -445,13 +456,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. @@ -466,7 +477,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. @@ -484,7 +495,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. @@ -498,7 +509,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. @@ -516,7 +527,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. @@ -553,9 +564,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. @@ -572,7 +583,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. @@ -591,7 +602,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. @@ -606,7 +617,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. @@ -625,7 +636,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. @@ -648,13 +659,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. @@ -669,7 +680,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. @@ -684,7 +695,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. @@ -699,7 +710,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. @@ -714,7 +725,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. @@ -729,7 +740,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. @@ -744,7 +755,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. @@ -759,7 +770,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. @@ -774,9 +785,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. @@ -793,7 +804,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. @@ -810,7 +821,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. @@ -827,7 +838,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. @@ -844,7 +855,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. @@ -861,7 +872,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. @@ -878,7 +889,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. @@ -895,7 +906,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. @@ -912,12 +923,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. @@ -927,15 +937,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 + +#### 8.1.1. RSA-KEM -This API can be used to create shared secret and its encapsulation using RSA-KEM function. +This API can be used to create shared secret and its encapsulation using RSA-KEM function. ```ballerina crypto:KeyStore keyStore = { @@ -945,10 +955,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 = { @@ -958,10 +968,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 = { @@ -981,11 +991,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 = { @@ -998,10 +1008,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 = { @@ -1014,10 +1024,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 = { @@ -1037,13 +1047,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. @@ -1057,8 +1067,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. @@ -1078,9 +1088,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. @@ -1098,8 +1108,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. @@ -1124,11 +1134,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. @@ -1137,6 +1147,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) @@ -1145,6 +1156,7 @@ public isolated function verifyBcrypt(string password, string hashedPassword) re ``` Example: + ```ballerina string password = "your-password"; // Hash with default work factor (12) @@ -1154,7 +1166,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. @@ -1164,6 +1176,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) @@ -1176,6 +1189,7 @@ public isolated function verifyArgon2(string password, string hashedPassword) re ``` Example: + ```ballerina string password = "your-password"; // Hash with default parameters @@ -1184,3 +1198,312 @@ string hashedPassword1 = check crypto:hashArgon2(password); string hashedPassword2 = check crypto:hashArgon2(password, iterations = 4, memory = 131072, parallelism = 8); boolean isValid = check crypto:verifyArgon2(password, hashedPassword1); ``` + +## 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](#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 + +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? + +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 (this is enforced by Ballerina) + +## 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.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-CBC 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:encryptAesCbc(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:encryptAesCbc(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 diff --git a/gradle.properties b/gradle.properties index e43639e8..d74ccff7 100644 --- a/gradle.properties +++ b/gradle.properties @@ -8,8 +8,12 @@ shadowJarPluginVersion=7.1.2 downloadPluginVersion=5.4.0 releasePluginVersion=2.8.0 ballerinaGradlePluginVersion=2.3.0 -nativeImageVersion=22.2.0 +substrateVmVersion=24.1.2 +jacksonDatabindVersion=2.17.2 ballerinaLangVersion=2201.12.0 stdlibTimeVersion=2.7.0 stdlibIoVersion=1.8.0 + +testngVersion=7.6.1 +balScanVersion=0.11.0 diff --git a/native/build.gradle b/native/build.gradle index 4c7a24b7..cb94fb22 100644 --- a/native/build.gradle +++ b/native/build.gradle @@ -33,7 +33,7 @@ dependencies { implementation group: 'org.bouncycastle', name: 'bcprov-jdk18on', version: "${bouncycastleVersion}" implementation group: 'org.bouncycastle', name: 'bcutil-jdk18on', version: "${bouncycastleVersion}" implementation group: 'org.bouncycastle', name: 'bcpg-jdk18on', version: "${bouncycastleVersion}" - compileOnly group: 'org.graalvm.nativeimage', name: 'svm', version: "${nativeImageVersion}" + compileOnly group: 'org.graalvm.nativeimage', name: 'svm', version: "${substrateVmVersion}" } checkstyle { diff --git a/native/src/main/java/module-info.java b/native/src/main/java/module-info.java index 6fca0bb3..d88aeddf 100644 --- a/native/src/main/java/module-info.java +++ b/native/src/main/java/module-info.java @@ -20,8 +20,7 @@ requires io.ballerina.stdlib.time; requires org.bouncycastle.provider; requires org.bouncycastle.pkix; - requires org.graalvm.sdk; - requires org.graalvm.nativeimage.builder; + requires org.graalvm.nativeimage; requires org.bouncycastle.pg; exports io.ballerina.stdlib.crypto.nativeimpl; exports io.ballerina.stdlib.crypto.svm; diff --git a/settings.gradle b/settings.gradle index 2dfaa987..7d952f1f 100644 --- a/settings.gradle +++ b/settings.gradle @@ -37,10 +37,14 @@ rootProject.name = 'crypto' include ':checkstyle' include ':crypto-native' include ':crypto-ballerina' +include ':crypto-compiler-plugin' +include ':crypto-compiler-plugin-tests' project(':checkstyle').projectDir = file("build-config${File.separator}checkstyle") project(':crypto-native').projectDir = file('native') project(':crypto-ballerina').projectDir = file('ballerina') +project(':crypto-compiler-plugin').projectDir = file('compiler-plugin') +project(':crypto-compiler-plugin-tests').projectDir = file('compiler-plugin-tests') gradleEnterprise { buildScan { From cef79b6541a4bd2f46ca49b31dddc8f338571cb6 Mon Sep 17 00:00:00 2001 From: Krishnananthalingam Tharmigan <63336800+TharmiganK@users.noreply.github.com> Date: Wed, 22 Oct 2025 11:01:46 +0530 Subject: [PATCH 2/2] Address review suggestion --- docs/spec/spec.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/docs/spec/spec.md b/docs/spec/spec.md index 6cd957ba..0409b64a 100644 --- a/docs/spec/spec.md +++ b/docs/spec/spec.md @@ -1,9 +1,9 @@ # Specification: Ballerina Crypto Library -_Owners_: @shafreenAnfar @bhashinee -_Reviewers_: @shafreenAnfar -_Created_: 2022/08/23 -_Updated_: 2025/10/17 +_Owners_: @shafreenAnfar @bhashinee \ +_Reviewers_: @shafreenAnfar \ +_Created_: 2022/08/23 \ +_Updated_: 2025/10/17 \ _Edition_: Swan Lake ## Introduction