From 6a7cb9bf9f454abbe63a5ea21f323114fd6df718 Mon Sep 17 00:00:00 2001 From: ravinperera00 Date: Mon, 30 Sep 2024 13:16:11 +0530 Subject: [PATCH 01/42] Migrate to Java 21 --- .github/workflows/build-timestamped-master.yml | 2 +- .github/workflows/build-with-bal-test-graalvm.yml | 2 +- .github/workflows/central-publish.yml | 2 +- .github/workflows/publish-release.yml | 2 +- .github/workflows/pull-request.yml | 2 +- .github/workflows/trivy-scan.yml | 2 +- README.md | 2 +- build-config/resources/Ballerina.toml | 12 ++++++------ gradle.properties | 4 ++-- native/build.gradle | 7 +++++-- .../stdlib/crypto/PgpDecryptionGenerator.java | 2 +- 11 files changed, 21 insertions(+), 18 deletions(-) diff --git a/.github/workflows/build-timestamped-master.yml b/.github/workflows/build-timestamped-master.yml index c8e42fa..86da481 100644 --- a/.github/workflows/build-timestamped-master.yml +++ b/.github/workflows/build-timestamped-master.yml @@ -12,5 +12,5 @@ jobs: call_workflow: name: Run Build Workflow if: ${{ github.repository_owner == 'ballerina-platform' }} - uses: ballerina-platform/ballerina-library/.github/workflows/build-timestamp-master-template.yml@main + uses: ballerina-platform/ballerina-library/.github/workflows/build-timestamp-master-template.yml@java21 secrets: inherit diff --git a/.github/workflows/build-with-bal-test-graalvm.yml b/.github/workflows/build-with-bal-test-graalvm.yml index 000baac..3d5d2e9 100644 --- a/.github/workflows/build-with-bal-test-graalvm.yml +++ b/.github/workflows/build-with-bal-test-graalvm.yml @@ -30,7 +30,7 @@ jobs: call_stdlib_workflow: name: Run StdLib Workflow if: ${{ github.event_name != 'schedule' || (github.event_name == 'schedule' && github.repository_owner == 'ballerina-platform') }} - uses: ballerina-platform/ballerina-library/.github/workflows/build-with-bal-test-graalvm-template.yml@main + uses: ballerina-platform/ballerina-library/.github/workflows/build-with-bal-test-graalvm-template.yml@java21 with: lang_tag: ${{ inputs.lang_tag }} lang_version: ${{ inputs.lang_version }} diff --git a/.github/workflows/central-publish.yml b/.github/workflows/central-publish.yml index 11922b5..ebe213e 100644 --- a/.github/workflows/central-publish.yml +++ b/.github/workflows/central-publish.yml @@ -15,7 +15,7 @@ jobs: call_workflow: name: Run Central Publish Workflow if: ${{ github.repository_owner == 'ballerina-platform' }} - uses: ballerina-platform/ballerina-library/.github/workflows/central-publish-template.yml@main + uses: ballerina-platform/ballerina-library/.github/workflows/central-publish-template.yml@java21 secrets: inherit with: environment: ${{ github.event.inputs.environment }} diff --git a/.github/workflows/publish-release.yml b/.github/workflows/publish-release.yml index 460928f..f84a847 100644 --- a/.github/workflows/publish-release.yml +++ b/.github/workflows/publish-release.yml @@ -9,7 +9,7 @@ jobs: call_workflow: name: Run Release Workflow if: ${{ github.repository_owner == 'ballerina-platform' }} - uses: ballerina-platform/ballerina-library/.github/workflows/release-package-template.yml@main + uses: ballerina-platform/ballerina-library/.github/workflows/release-package-template.yml@java21 secrets: inherit with: package-name: crypto diff --git a/.github/workflows/pull-request.yml b/.github/workflows/pull-request.yml index 3b7c146..c534e2f 100644 --- a/.github/workflows/pull-request.yml +++ b/.github/workflows/pull-request.yml @@ -10,5 +10,5 @@ jobs: call_workflow: name: Run PR Build Workflow if: ${{ github.repository_owner == 'ballerina-platform' }} - uses: ballerina-platform/ballerina-library/.github/workflows/pull-request-build-template.yml@main + uses: ballerina-platform/ballerina-library/.github/workflows/pull-request-build-template.yml@java21 secrets: inherit diff --git a/.github/workflows/trivy-scan.yml b/.github/workflows/trivy-scan.yml index 458aab5..d91a5f3 100644 --- a/.github/workflows/trivy-scan.yml +++ b/.github/workflows/trivy-scan.yml @@ -9,5 +9,5 @@ jobs: call_workflow: name: Run Trivy Scan Workflow if: ${{ github.repository_owner == 'ballerina-platform' }} - uses: ballerina-platform/ballerina-library/.github/workflows/trivy-scan-template.yml@main + uses: ballerina-platform/ballerina-library/.github/workflows/trivy-scan-template.yml@java21 secrets: inherit diff --git a/README.md b/README.md index 8cdcb70..0ab9206 100644 --- a/README.md +++ b/README.md @@ -54,7 +54,7 @@ This repository only contains the source code for the module. ### Set up the prerequisites -1. Download and install Java SE Development Kit (JDK) version 17 (from one of the following locations). +1. Download and install Java SE Development Kit (JDK) version 21 (from one of the following locations). * [Oracle](https://www.oracle.com/java/technologies/downloads/) diff --git a/build-config/resources/Ballerina.toml b/build-config/resources/Ballerina.toml index 2f59bda..06f57fe 100644 --- a/build-config/resources/Ballerina.toml +++ b/build-config/resources/Ballerina.toml @@ -9,34 +9,34 @@ icon = "icon.png" license = ["Apache-2.0"] distribution = "2201.9.0" -[platform.java17] +[platform.java21] graalvmCompatible = true -[[platform.java17.dependency]] +[[platform.java21.dependency]] groupId = "io.ballerina.stdlib" artifactId = "crypto-native" version = "@toml.version@" path = "../native/build/libs/crypto-native-@project.version@.jar" -[[platform.java17.dependency]] +[[platform.java21.dependency]] groupId = "org.bouncycastle" artifactId = "bcpkix-jdk18on" version = "@bouncycastle.version@" path = "./lib/bcpkix-jdk18on-@bouncycastle.version@.jar" -[[platform.java17.dependency]] +[[platform.java21.dependency]] groupId = "org.bouncycastle" artifactId = "bcprov-jdk18on" version = "@bouncycastle.version@" path = "./lib/bcprov-jdk18on-@bouncycastle.version@.jar" -[[platform.java17.dependency]] +[[platform.java21.dependency]] groupId = "org.bouncycastle" artifactId = "bcutil-jdk18on" version = "@bouncycastle.version@" path = "./lib/bcutil-jdk18on-@bouncycastle.version@.jar" -[[platform.java17.dependency]] +[[platform.java21.dependency]] groupId = "org.bouncycastle" artifactId = "bcpg-jdk18on" version = "@bouncycastle.version@" diff --git a/gradle.properties b/gradle.properties index ac5ea3e..af8d0c8 100644 --- a/gradle.properties +++ b/gradle.properties @@ -3,12 +3,12 @@ group=io.ballerina.stdlib version=2.7.3-SNAPSHOT puppycrawlCheckstyleVersion=10.12.0 bouncycastleVersion=1.78 -githubSpotbugsVersion=5.0.14 +githubSpotbugsVersion=6.0.18 githubShadowVersion=7.1.2 undercouchDownloadVersion=5.4.0 researchgateReleaseVersion=2.8.0 ballerinaGradlePluginVersion=2.0.1 nativeImageVersion=22.2.0 -ballerinaLangVersion=2201.9.0 +ballerinaLangVersion=2201.10.0-20240926-231800-8a5a4343 stdlibTimeVersion=2.4.0 diff --git a/native/build.gradle b/native/build.gradle index ed24295..55e1105 100644 --- a/native/build.gradle +++ b/native/build.gradle @@ -45,8 +45,11 @@ checkstyle { checkstyleMain.dependsOn(":checkstyle:downloadCheckstyleRuleFiles") spotbugsMain { - effort "max" - reportLevel "low" + 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 diff --git a/native/src/main/java/io/ballerina/stdlib/crypto/PgpDecryptionGenerator.java b/native/src/main/java/io/ballerina/stdlib/crypto/PgpDecryptionGenerator.java index 3b18829..3a4a7bf 100644 --- a/native/src/main/java/io/ballerina/stdlib/crypto/PgpDecryptionGenerator.java +++ b/native/src/main/java/io/ballerina/stdlib/crypto/PgpDecryptionGenerator.java @@ -53,7 +53,7 @@ * * @since 2.7.0 */ -public class PgpDecryptionGenerator { +public final class PgpDecryptionGenerator { static { if (Objects.isNull(Security.getProvider(BouncyCastleProvider.PROVIDER_NAME))) { From 0c230e218c5f2cbc0dde1f6d83deca05fe19f045 Mon Sep 17 00:00:00 2001 From: TharmiganK Date: Wed, 2 Oct 2024 11:55:10 +0530 Subject: [PATCH 02/42] [Automated] Update the native jar versions --- ballerina/Ballerina.toml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/ballerina/Ballerina.toml b/ballerina/Ballerina.toml index 0ea61d0..74a87ba 100644 --- a/ballerina/Ballerina.toml +++ b/ballerina/Ballerina.toml @@ -1,7 +1,7 @@ [package] org = "ballerina" name = "crypto" -version = "2.7.2" +version = "2.7.3" 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.java17.dependency]] groupId = "io.ballerina.stdlib" artifactId = "crypto-native" -version = "2.7.2" -path = "../native/build/libs/crypto-native-2.7.2.jar" +version = "2.7.3" +path = "../native/build/libs/crypto-native-2.7.3-SNAPSHOT.jar" [[platform.java17.dependency]] groupId = "org.bouncycastle" From 85c3173ce3200d2c6ff5c17dd19e93b5096691c9 Mon Sep 17 00:00:00 2001 From: TharmiganK Date: Wed, 2 Oct 2024 11:58:35 +0530 Subject: [PATCH 03/42] [Automated] Update the native jar versions --- ballerina/Dependencies.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ballerina/Dependencies.toml b/ballerina/Dependencies.toml index 78f2c21..271dd6a 100644 --- a/ballerina/Dependencies.toml +++ b/ballerina/Dependencies.toml @@ -10,7 +10,7 @@ distribution-version = "2201.9.0" [[package]] org = "ballerina" name = "crypto" -version = "2.7.2" +version = "2.7.3" dependencies = [ {org = "ballerina", name = "jballerina.java"}, {org = "ballerina", name = "lang.array"}, From 3ac65fde4ec9172f9834471b14fa6599d38a77eb Mon Sep 17 00:00:00 2001 From: TharmiganK Date: Wed, 2 Oct 2024 14:38:11 +0530 Subject: [PATCH 04/42] Add PGP encrypt/decrypt functions with files --- ballerina/encrypt_decrypt.bal | 31 +++++++++++++++++++ .../stdlib/crypto/PgpDecryptionGenerator.java | 8 +++++ .../stdlib/crypto/PgpEncryptionGenerator.java | 21 ++++++++----- .../stdlib/crypto/nativeimpl/Decrypt.java | 20 ++++++++++++ .../stdlib/crypto/nativeimpl/Encrypt.java | 25 +++++++++++++++ 5 files changed, 98 insertions(+), 7 deletions(-) diff --git a/ballerina/encrypt_decrypt.bal b/ballerina/encrypt_decrypt.bal index 0002f4f..adff575 100644 --- a/ballerina/encrypt_decrypt.bal +++ b/ballerina/encrypt_decrypt.bal @@ -260,6 +260,21 @@ public isolated function encryptPgp(byte[] plainText, string publicKeyPath, *Opt 'class: "io.ballerina.stdlib.crypto.nativeimpl.Encrypt" } external; +# Writes the PGP-encrypted value of the given data to a file specified by the output file path. +# ```ballerina +# check crypto:encryptPgpAsFile("input.txt", "public_key.asc", "output.txt"); +# ``` +# +# + inputFilePath - Path to the input file +# + publicKeyPath - Path to the public key +# + outputFilePath - Path to the output file +# + options - PGP encryption options +# + return - A `crypto:Error` will be returned if the process fails +public isolated function encryptPgpAsFile(string inputFilePath, string publicKeyPath, string outputFilePath, + *Options options) returns Error? = @java:Method { + 'class: "io.ballerina.stdlib.crypto.nativeimpl.Encrypt" +} external; + # Returns the PGP-decrypted value of the given PGP-encrypted data. # ```ballerina # byte[] message = "Hello Ballerina!".toBytes(); @@ -278,3 +293,19 @@ public isolated function decryptPgp(byte[] cipherText, string privateKeyPath, by name: "decryptPgp", 'class: "io.ballerina.stdlib.crypto.nativeimpl.Decrypt" } external; + +# Writes the PGP-decrypted value of the given data to a file specified by the output file path. +# ```ballerina +# byte[] passphrase = check io:fileReadBytes("pass_phrase.txt"); +# check crypto:decryptPgpAsFile("input.txt", "private_key.asc", passphrase, "output.txt"); +# ``` +# +# + inputFilePath - Path to the input file +# + privateKeyPath - Path to the private key +# + passphrase - passphrase of the private key +# + outputFilePath - Path to the output file +# + return - A `crypto:Error` will be returned if the process fails +public isolated function decryptPgpAsFile(string inputFilePath, string privateKeyPath, byte[] passphrase, + string outputFilePath) returns Error? = @java:Method { + 'class: "io.ballerina.stdlib.crypto.nativeimpl.Decrypt" +} external; diff --git a/native/src/main/java/io/ballerina/stdlib/crypto/PgpDecryptionGenerator.java b/native/src/main/java/io/ballerina/stdlib/crypto/PgpDecryptionGenerator.java index 3b18829..1cba6a8 100644 --- a/native/src/main/java/io/ballerina/stdlib/crypto/PgpDecryptionGenerator.java +++ b/native/src/main/java/io/ballerina/stdlib/crypto/PgpDecryptionGenerator.java @@ -43,6 +43,8 @@ import java.io.InputStream; import java.io.OutputStream; import java.nio.charset.StandardCharsets; +import java.nio.file.Files; +import java.nio.file.Path; import java.security.Security; import java.util.Iterator; import java.util.Objects; @@ -123,6 +125,12 @@ public Object decrypt(byte[] encryptedBytes) throws PGPException, IOException { } } + public void decrypt(InputStream encryptedIn, String outputPath) throws PGPException, IOException { + try (OutputStream outputStream = Files.newOutputStream(Path.of(outputPath))) { + decryptStream(encryptedIn, outputStream); + } + } + private static void decrypt(OutputStream clearOut, Optional pgpPrivateKey, PGPPublicKeyEncryptedData publicKeyEncryptedData) throws IOException, PGPException { if (pgpPrivateKey.isPresent()) { diff --git a/native/src/main/java/io/ballerina/stdlib/crypto/PgpEncryptionGenerator.java b/native/src/main/java/io/ballerina/stdlib/crypto/PgpEncryptionGenerator.java index abfced9..6774966 100644 --- a/native/src/main/java/io/ballerina/stdlib/crypto/PgpEncryptionGenerator.java +++ b/native/src/main/java/io/ballerina/stdlib/crypto/PgpEncryptionGenerator.java @@ -38,6 +38,8 @@ import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; +import java.nio.file.Files; +import java.nio.file.Path; import java.security.SecureRandom; import java.security.Security; import java.time.LocalDateTime; @@ -76,7 +78,7 @@ public PgpEncryptionGenerator(int compressionAlgorithm, int symmetricKeyAlgorith this.withIntegrityCheck = withIntegrityCheck; } - private void encryptStream(OutputStream encryptOut, InputStream clearIn, long length, InputStream publicKeyIn) + private void encryptStream(OutputStream encryptOut, InputStream clearIn, InputStream publicKeyIn) throws IOException, PGPException { PGPCompressedDataGenerator compressedDataGenerator = new PGPCompressedDataGenerator(compressionAlgorithm); @@ -95,7 +97,7 @@ private void encryptStream(OutputStream encryptOut, InputStream clearIn, long le } try (OutputStream cipherOutStream = pgpEncryptedDataGenerator.open(encryptOut, new byte[BUFFER_SIZE])) { - copyAsLiteralData(compressedDataGenerator.open(cipherOutStream), clearIn, length); + copyAsLiteralData(compressedDataGenerator.open(cipherOutStream), clearIn); compressedDataGenerator.close(); } encryptOut.close(); @@ -105,11 +107,18 @@ private void encryptStream(OutputStream encryptOut, InputStream clearIn, long le public Object encrypt(byte[] clearData, InputStream publicKeyIn) throws PGPException, IOException { try (ByteArrayInputStream inputStream = new ByteArrayInputStream(clearData); ByteArrayOutputStream outputStream = new ByteArrayOutputStream()) { - encryptStream(outputStream, inputStream, clearData.length, publicKeyIn); + encryptStream(outputStream, inputStream, publicKeyIn); return ValueCreator.createArrayValue(outputStream.toByteArray()); } } + public void encrypt(InputStream inputStream, InputStream publicKeyIn, String outputPath) + throws PGPException, IOException { + try (OutputStream outputStream = Files.newOutputStream(Path.of(outputPath))) { + encryptStream(outputStream, inputStream, publicKeyIn); + } + } + private static PGPPublicKey getPublicKey(InputStream keyInputStream) throws IOException, PGPException { PGPPublicKeyRingCollection pgpPublicKeyRings = new PGPPublicKeyRingCollection( PGPUtil.getDecoderStream(keyInputStream), new JcaKeyFingerprintCalculator()); @@ -124,7 +133,7 @@ private static PGPPublicKey getPublicKey(InputStream keyInputStream) throws IOEx throw new PGPException("Invalid public key"); } - private static void copyAsLiteralData(OutputStream outputStream, InputStream in, long length) + private static void copyAsLiteralData(OutputStream outputStream, InputStream in) throws IOException { PGPLiteralDataGenerator lData = new PGPLiteralDataGenerator(); byte[] buff = new byte[PgpEncryptionGenerator.BUFFER_SIZE]; @@ -133,10 +142,8 @@ private static void copyAsLiteralData(OutputStream outputStream, InputStream in, InputStream inputStream = in) { int len; - long totalBytesWritten = 0L; - while (totalBytesWritten <= length && (len = inputStream.read(buff)) > 0) { + while ((len = inputStream.read(buff)) > 0) { pOut.write(buff, 0, len); - totalBytesWritten += len; } } finally { Arrays.fill(buff, (byte) 0); diff --git a/native/src/main/java/io/ballerina/stdlib/crypto/nativeimpl/Decrypt.java b/native/src/main/java/io/ballerina/stdlib/crypto/nativeimpl/Decrypt.java index ffa4adc..5aa7cc3 100644 --- a/native/src/main/java/io/ballerina/stdlib/crypto/nativeimpl/Decrypt.java +++ b/native/src/main/java/io/ballerina/stdlib/crypto/nativeimpl/Decrypt.java @@ -100,4 +100,24 @@ public static Object decryptPgp(BArray cipherTextValue, BString privateKeyPath, return CryptoUtils.createError("Error occurred while PGP decrypt: " + e.getMessage()); } } + + public static Object decryptPgpAsFile(BString inputFilePath, BString privateKeyPath, BArray passphrase, + BString outputFilePath) { + byte[] passphraseInBytes = passphrase.getBytes(); + byte[] privateKey; + try { + privateKey = Files.readAllBytes(Path.of(privateKeyPath.toString())); + } catch (IOException e) { + return CryptoUtils.createError("Error occurred while reading public key: " + e.getMessage()); + } + + try (InputStream keyStream = new ByteArrayInputStream(privateKey); + InputStream cipherTextStream = Files.newInputStream(Path.of(inputFilePath.toString()))) { + PgpDecryptionGenerator pgpDecryptionGenerator = new PgpDecryptionGenerator(keyStream, passphraseInBytes); + pgpDecryptionGenerator.decrypt(cipherTextStream, outputFilePath.getValue()); + return null; + } catch (IOException | PGPException e) { + return CryptoUtils.createError("Error occurred while PGP decrypt: " + e.getMessage()); + } + } } diff --git a/native/src/main/java/io/ballerina/stdlib/crypto/nativeimpl/Encrypt.java b/native/src/main/java/io/ballerina/stdlib/crypto/nativeimpl/Encrypt.java index eb75baa..2f4dc48 100644 --- a/native/src/main/java/io/ballerina/stdlib/crypto/nativeimpl/Encrypt.java +++ b/native/src/main/java/io/ballerina/stdlib/crypto/nativeimpl/Encrypt.java @@ -116,4 +116,29 @@ public static Object encryptPgp(BArray plainTextValue, BString publicKeyPath, BM return CryptoUtils.createError("Error occurred while PGP encrypt: " + e.getMessage()); } } + + public static Object encryptPgpAsFile(BString inputFilePath, BString publicKeyPath, BString outputFilePath, + BMap options) { + byte[] publicKey; + try { + publicKey = Files.readAllBytes(Path.of(publicKeyPath.toString())); + } catch (IOException e) { + return CryptoUtils.createError("Error occurred while reading public key: " + e.getMessage()); + } + + try (InputStream publicKeyStream = new ByteArrayInputStream(publicKey); + InputStream inputStream = Files.newInputStream(Path.of(inputFilePath.toString())) + ) { + PgpEncryptionGenerator pgpEncryptionGenerator = new PgpEncryptionGenerator( + Integer.parseInt(options.get(COMPRESSION_ALGORITHM).toString()), + Integer.parseInt(options.get(SYMMETRIC_KEY_ALGORITHM).toString()), + Boolean.parseBoolean(options.get(ARMOR).toString()), + Boolean.parseBoolean(options.get(WITH_INTEGRITY_CHECK).toString()) + ); + pgpEncryptionGenerator.encrypt(inputStream, publicKeyStream, outputFilePath.getValue()); + return null; + } catch (IOException | PGPException e) { + return CryptoUtils.createError("Error occurred while PGP encrypt: " + e.getMessage()); + } + } } From 0fbae6bd6b8e121ce926c6de12767663beac88c9 Mon Sep 17 00:00:00 2001 From: Krishnananthalingam Tharmigan <63336800+TharmiganK@users.noreply.github.com> Date: Wed, 2 Oct 2024 15:20:47 +0530 Subject: [PATCH 05/42] Apply suggestions from code review Co-authored-by: DimuthuMadushan <35717653+DimuthuMadushan@users.noreply.github.com> --- .../java/io/ballerina/stdlib/crypto/nativeimpl/Decrypt.java | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/native/src/main/java/io/ballerina/stdlib/crypto/nativeimpl/Decrypt.java b/native/src/main/java/io/ballerina/stdlib/crypto/nativeimpl/Decrypt.java index 5aa7cc3..82914fe 100644 --- a/native/src/main/java/io/ballerina/stdlib/crypto/nativeimpl/Decrypt.java +++ b/native/src/main/java/io/ballerina/stdlib/crypto/nativeimpl/Decrypt.java @@ -90,7 +90,7 @@ public static Object decryptPgp(BArray cipherTextValue, BString privateKeyPath, try { privateKey = Files.readAllBytes(Path.of(privateKeyPath.toString())); } catch (IOException e) { - return CryptoUtils.createError("Error occurred while reading public key: " + e.getMessage()); + return CryptoUtils.createError("Error occurred while reading private key: " + e.getMessage()); } try (InputStream keyStream = new ByteArrayInputStream(privateKey)) { @@ -108,11 +108,11 @@ public static Object decryptPgpAsFile(BString inputFilePath, BString privateKeyP try { privateKey = Files.readAllBytes(Path.of(privateKeyPath.toString())); } catch (IOException e) { - return CryptoUtils.createError("Error occurred while reading public key: " + e.getMessage()); + return CryptoUtils.createError("Error occurred while reading private key: " + e.getMessage()); } try (InputStream keyStream = new ByteArrayInputStream(privateKey); - InputStream cipherTextStream = Files.newInputStream(Path.of(inputFilePath.toString()))) { + InputStream cipherTextStream = Files.newInputStream(Path.of(inputFilePath.toString()))) { PgpDecryptionGenerator pgpDecryptionGenerator = new PgpDecryptionGenerator(keyStream, passphraseInBytes); pgpDecryptionGenerator.decrypt(cipherTextStream, outputFilePath.getValue()); return null; From ae7af8c03207e2ec0bd87339642403d000f6c814 Mon Sep 17 00:00:00 2001 From: TharmiganK Date: Wed, 2 Oct 2024 15:53:46 +0530 Subject: [PATCH 06/42] [Automated] Update the native jar versions --- ballerina/Dependencies.toml | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/ballerina/Dependencies.toml b/ballerina/Dependencies.toml index 271dd6a..52cab4f 100644 --- a/ballerina/Dependencies.toml +++ b/ballerina/Dependencies.toml @@ -12,6 +12,7 @@ org = "ballerina" name = "crypto" version = "2.7.3" dependencies = [ + {org = "ballerina", name = "io"}, {org = "ballerina", name = "jballerina.java"}, {org = "ballerina", name = "lang.array"}, {org = "ballerina", name = "test"}, @@ -21,6 +22,19 @@ modules = [ {org = "ballerina", packageName = "crypto", moduleName = "crypto"} ] +[[package]] +org = "ballerina" +name = "io" +version = "1.6.1" +scope = "testOnly" +dependencies = [ + {org = "ballerina", name = "jballerina.java"}, + {org = "ballerina", name = "lang.value"} +] +modules = [ + {org = "ballerina", packageName = "io", moduleName = "io"} +] + [[package]] org = "ballerina" name = "jballerina.java" @@ -67,6 +81,15 @@ name = "lang.object" version = "0.0.0" scope = "testOnly" +[[package]] +org = "ballerina" +name = "lang.value" +version = "0.0.0" +scope = "testOnly" +dependencies = [ + {org = "ballerina", name = "jballerina.java"} +] + [[package]] org = "ballerina" name = "test" From 3deeba87128a87c7bff8dafe7f26f334365d78d5 Mon Sep 17 00:00:00 2001 From: TharmiganK Date: Wed, 2 Oct 2024 16:06:07 +0530 Subject: [PATCH 07/42] Add test cases --- ballerina/encrypt_decrypt.bal | 2 + ballerina/private_public_key.bal | 4 +- ballerina/tests/encrypt_decrypt_pgp_test.bal | 58 +++++++++++++++++++- ballerina/tests/resources/sample.txt | 12 ++++ ballerina/tests/test_utils.bal | 4 ++ 5 files changed, 77 insertions(+), 3 deletions(-) create mode 100644 ballerina/tests/resources/sample.txt diff --git a/ballerina/encrypt_decrypt.bal b/ballerina/encrypt_decrypt.bal index adff575..f2f7058 100644 --- a/ballerina/encrypt_decrypt.bal +++ b/ballerina/encrypt_decrypt.bal @@ -276,6 +276,7 @@ public isolated function encryptPgpAsFile(string inputFilePath, string publicKey } external; # Returns the PGP-decrypted value of the given PGP-encrypted data. +# If the output file already exists, it will be overwritten. # ```ballerina # byte[] message = "Hello Ballerina!".toBytes(); # byte[] cipherText = check crypto:encryptPgp(message, "public_key.asc"); @@ -295,6 +296,7 @@ public isolated function decryptPgp(byte[] cipherText, string privateKeyPath, by } external; # Writes the PGP-decrypted value of the given data to a file specified by the output file path. +# If the output file already exists, it will be overwritten. # ```ballerina # byte[] passphrase = check io:fileReadBytes("pass_phrase.txt"); # check crypto:decryptPgpAsFile("input.txt", "private_key.asc", passphrase, "output.txt"); diff --git a/ballerina/private_public_key.bal b/ballerina/private_public_key.bal index 3a963ed..48d5900 100644 --- a/ballerina/private_public_key.bal +++ b/ballerina/private_public_key.bal @@ -176,7 +176,7 @@ public isolated function decodeRsaPrivateKeyFromKeyFile(string keyFile, string? # crypto:PrivateKey privateKey = check crypto:decodeRsaPrivateKeyFromContent(keyFileContent, "keyPassword"); # ``` # -# + keyFile - Private key content as a byte array +# + content - Private key content as a byte array # + keyPassword - Password of the private key if it is encrypted # + return - Reference to the private key or else a `crypto:Error` if the private key was unreadable public isolated function decodeRsaPrivateKeyFromContent(byte[] content, string? keyPassword = ()) returns PrivateKey|Error = @java:Method { @@ -311,7 +311,7 @@ public isolated function decodeRsaPublicKeyFromCertFile(string certFile) returns # crypto:PublicKey publicKey = check crypto:decodeRsaPublicKeyFromContent(certContent); # ``` # -# + certFile - The certificate content as a byte array +# + content - The certificate content as a byte array # + return - Reference to the public key or else a `crypto:Error` if the public key was unreadable public isolated function decodeRsaPublicKeyFromContent(byte[] content) returns PublicKey|Error = @java:Method { 'class: "io.ballerina.stdlib.crypto.nativeimpl.Decode" diff --git a/ballerina/tests/encrypt_decrypt_pgp_test.bal b/ballerina/tests/encrypt_decrypt_pgp_test.bal index 2451177..686dd54 100644 --- a/ballerina/tests/encrypt_decrypt_pgp_test.bal +++ b/ballerina/tests/encrypt_decrypt_pgp_test.bal @@ -14,6 +14,7 @@ // specific language governing permissions and limitations // under the License. +import ballerina/io; import ballerina/test; @test:Config {} @@ -55,8 +56,63 @@ isolated function testNegativeEncryptAndDecryptWithPgpInvalidPassphrase() return byte[]|Error plainText = decryptPgp(cipherText, PGP_PRIVATE_KEY_PATH, passphrase); if plainText is Error { test:assertEquals(plainText.message(), - "Error occurred while PGP decrypt: checksum mismatch at in checksum of 20 bytes"); + "Error occurred while PGP decrypt: checksum mismatch at in checksum of 20 bytes"); } else { test:assertFail("Should return a crypto Error"); } } + +@test:Config { + serialExecution: true +} +isolated function testEncryptAndDecryptFileWithPgp() returns error? { + byte[] passphrase = "qCr3bv@5mj5n4eY".toBytes(); + check encryptPgpAsFile(SAMPLE_TEXT, PGP_PUBLIC_KEY_PATH, TARGET_ENCRYPTION_OUTPUT); + check decryptPgpAsFile(TARGET_ENCRYPTION_OUTPUT, PGP_PRIVATE_KEY_PATH, passphrase, TARGET_DECRYPTION_OUTPUT); + test:assertTrue(check isSameFileContent(SAMPLE_TEXT, TARGET_DECRYPTION_OUTPUT)); +} + +@test:Config { + serialExecution: true +} +isolated function testEncryptAndDecryptFileWithPgpWithOptions() returns error? { + byte[] passphrase = "qCr3bv@5mj5n4eY".toBytes(); + check encryptPgpAsFile(SAMPLE_TEXT, PGP_PUBLIC_KEY_PATH, TARGET_ENCRYPTION_OUTPUT, symmetricKeyAlgorithm = AES_128, armor = false); + check decryptPgpAsFile(TARGET_ENCRYPTION_OUTPUT, PGP_PRIVATE_KEY_PATH, passphrase, TARGET_DECRYPTION_OUTPUT); + test:assertTrue(check isSameFileContent(SAMPLE_TEXT, TARGET_DECRYPTION_OUTPUT)); +} + +@test:Config { + serialExecution: true +} +isolated function testNegativeEncryptAndDecryptFileWithPgpInvalidPrivateKey() returns error? { + byte[] passphrase = "p7S5@T2MRFD9TQb".toBytes(); + check encryptPgpAsFile(SAMPLE_TEXT, PGP_PUBLIC_KEY_PATH, TARGET_ENCRYPTION_OUTPUT); + error? err = decryptPgpAsFile(TARGET_ENCRYPTION_OUTPUT, PGP_INVALID_PRIVATE_KEY_PATH, passphrase, TARGET_DECRYPTION_OUTPUT); + if err is Error { + test:assertEquals(err.message(), "Error occurred while PGP decrypt: Could Not Extract private key"); + } else { + test:assertFail("Should return a crypto Error"); + } +} + +@test:Config { + serialExecution: true +} +isolated function testNegativeEncryptAndDecryptFileWithPgpInvalidPassphrase() returns error? { + byte[] passphrase = "p7S5@T2MRFD9TQb".toBytes(); + check encryptPgpAsFile(SAMPLE_TEXT, PGP_PUBLIC_KEY_PATH, TARGET_ENCRYPTION_OUTPUT); + error? err = decryptPgpAsFile(TARGET_ENCRYPTION_OUTPUT, PGP_PRIVATE_KEY_PATH, passphrase, TARGET_DECRYPTION_OUTPUT); + if err is Error { + test:assertEquals(err.message(), + "Error occurred while PGP decrypt: checksum mismatch at in checksum of 20 bytes"); + } else { + test:assertFail("Should return a crypto Error"); + } +} + +isolated function isSameFileContent(string inputFilePath, string outputFilePath) returns boolean|error { + byte[] input = check io:fileReadBytes(inputFilePath); + byte[] output = check io:fileReadBytes(outputFilePath); + return input.toBase64() == output.toBase64(); +} diff --git a/ballerina/tests/resources/sample.txt b/ballerina/tests/resources/sample.txt new file mode 100644 index 0000000..4556189 --- /dev/null +++ b/ballerina/tests/resources/sample.txt @@ -0,0 +1,12 @@ + +Ballerina is an open-source programming language designed for cloud-native application development. It combines features for integration, service orchestration, and network interaction, with a focus on ease of use for building APIs, managing data, and deploying in distributed environments. Ballerina's syntax and built-in concurrency support make it well-suited for creating robust, scalable, and secure services. + +Ballerina adopts a developer-friendly approach by incorporating modern programming constructs, such as structural typing, flexible JSON handling, and a familiar C-style syntax, which reduces the learning curve for developers. The language has first-class support for network primitives, allowing developers to directly work with network protocols like HTTP, WebSockets, and gRPC without the need for additional libraries. This direct handling of network interactions makes Ballerina ideal for writing microservices and integrating with other systems effortlessly. + +Ballerina also features built-in support for distributed transactions, reliable messaging, and data transformations, making it suitable for integration-heavy applications. Its built-in observability tools, including metrics, logs, and distributed tracing, help developers monitor and debug applications efficiently. Ballerina is inherently cloud-native, with easy containerization and Kubernetes deployment support, simplifying the process of deploying services in modern cloud environments. + +The concurrency model in Ballerina is based on the concept of "strands," which are lightweight threads managed by the language runtime. This model allows developers to write concurrent code using simple constructs, such as asynchronous functions and workers, without worrying about low-level threading concerns. This makes it easier to develop applications that are responsive and scalable, capable of handling high loads and concurrent user interactions. + +Ballerina’s ecosystem includes various tools, such as the Ballerina Central registry, which provides a platform for sharing and discovering packages. The language’s visual representation of code through sequence diagrams is another unique feature, enabling both developers and non-developers to better understand program behavior, especially for integration logic. Ballerina's compiler can generate these diagrams automatically, which is beneficial for documentation and analysis of workflows. + +Furthermore, Ballerina's support for data-oriented programming makes it easy to transform and manipulate structured data formats like JSON, XML, and SQL. This, along with the language’s built-in type system that directly represents these data types, reduces the need for complex data mapping and serialization tasks. With support for RESTful APIs, GraphQL, and multiple database connectors, Ballerina is designed to provide seamless integration capabilities, making it an excellent choice for businesses looking to modernize their IT landscape with cloud-native services. diff --git a/ballerina/tests/test_utils.bal b/ballerina/tests/test_utils.bal index e12598c..53655ae 100644 --- a/ballerina/tests/test_utils.bal +++ b/ballerina/tests/test_utils.bal @@ -38,3 +38,7 @@ const string PGP_PUBLIC_KEY_PATH = "tests/resources/public_key.asc"; const string PGP_PRIVATE_KEY_PATH = "tests/resources/private_key.asc"; const string PGP_INVALID_PRIVATE_KEY_PATH = "tests/resources/invalid_private_key.asc"; const string PGP_PRIVATE_KEY_PASSPHRASE_PATH = "tests/resources/pgp_private_key_passphrase.txt"; + +const string SAMPLE_TEXT = "tests/resources/sample.txt"; +const string TARGET_ENCRYPTION_OUTPUT = "target/encrypted_output.txt"; +const string TARGET_DECRYPTION_OUTPUT = "target/decrypted_output.txt"; From c930edd6c33ee0a9bb253cf16c750d07f3b367ef Mon Sep 17 00:00:00 2001 From: TharmiganK Date: Wed, 2 Oct 2024 16:08:49 +0530 Subject: [PATCH 08/42] Update change log --- changelog.md | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/changelog.md b/changelog.md index f4a0917..57b2a5e 100644 --- a/changelog.md +++ b/changelog.md @@ -5,6 +5,12 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), ## [Unreleased] +### Added + +- [Introduce new APIs to support PGP encryption and decryption with files](https://github.com/ballerina-platform/ballerina-library/issues/7064) + +## [2.7.2] - 2024-05-30 + ### Added - [Implement the support for reading private/public keys from the content](https://github.com/ballerina-platform/ballerina-library/issues/6513) From 04ba4ca83b46d98104d56ae8e6ec1a850b35aae7 Mon Sep 17 00:00:00 2001 From: TharmiganK Date: Wed, 2 Oct 2024 16:22:03 +0530 Subject: [PATCH 09/42] Update spec --- docs/spec/spec.md | 70 ++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 69 insertions(+), 1 deletion(-) diff --git a/docs/spec/spec.md b/docs/spec/spec.md index 7c5850a..8a1f6b7 100644 --- a/docs/spec/spec.md +++ b/docs/spec/spec.md @@ -51,18 +51,19 @@ The conforming implementation of the specification is released and included in t * 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) @@ -502,6 +503,46 @@ foreach int i in 0...15 { byte[] cipherText = check crypto:encryptAesGcm(data, key, initialVector); ``` +#### 5.1.5. [PGP](#515-pgp) + +This API can be used to create the PGP-encrypted value for the given data. + +```ballerina +string input = "Hello Ballerina"; +byte[] data = input.toBytes(); +string publicKeyPath = "/path/to/publickey.asc"; + +byte[] cipherText = check crypto:encryptPgp(data, publicKeyPath); +``` + +The following encryption options can be configured in the PGP encryption. + +| Option | Description | Default Value | +|-----------------------|-------------------------------------------------------------------|---------------| +| compressionAlgorithm | Specifies the compression algorithm used for PGP encryption | ZIP | +| symmetricKeyAlgorithm | Specifies the symmetric key algorithm used for encryption | AES_256 | +| armor | Indicates whether ASCII armor is enabled for the encrypted output | true | +| withIntegrityCheck | Indicates whether integrity check is included in the encryption | true | + +```ballerina +string input = "Hello Ballerina"; +byte[] data = input.toBytes(); +string publicKeyPath = "/path/to/publickey.asc"; + +byte[] cipherText = check crypto:encryptPgp(data, publicKeyPath, armor = false); +``` + +In addition to the above, the following API can be used to read a content from a file, encrypt it using the PGP public +key and write the encrypted content to the file specified. + +```ballerina +string inputFilePath = "/path/to/input.txt"; +string outputFilePath = "/path/to/output.txt"; +string publicKeyPath = "/path/to/publickey.asc"; + +check crypto:encryptPgpAsFile(inputFilePath, publicKeyPath, outputFilePath); +``` + ### 5.2. [Decryption](#52-decryption) #### 5.2.1. [RSA-ECB](#521-rsa-ecb) @@ -574,6 +615,33 @@ byte[] cipherText = check crypto:encryptAesGcm(data, key, initialVector); byte[] plainText = check crypto:decryptAesGcm(cipherText, key, initialVector); ``` +#### 5.2.5. [PGP](#525-pgp) + +This API can be used to create the PGP-decrypted value for the given PGP-encrypted data. + +```ballerina +string input = "Hello Ballerina"; +byte[] data = input.toBytes(); +string publicKeyPath = "/path/to/publickey.asc"; +string privateKeyPath = "/path/to/privatekey.asc"; +string passPhrase = "passphrase"; + +byte[] cipherText = check crypto:encryptPgp(data, publicKeyPath); +byte[] plainText = check crypto:decryptPgp(cipherText, privateKeyPath, passPhrase.toBytes()); +``` + +In addition to the above, the following API can be used to read an encrypted content from a file, decrypt it using the +PGP private key and passphrase and write the decrypted content to the file specified. + +```ballerina +string inputFilePath = "/path/to/input.txt"; +string outputFilePath = "/path/to/output.txt"; +string privateKeyPath = "/path/to/privatekey.asc"; +string passPhrase = "passphrase"; + +check crypto:decryptPgpAsFile(inputFilePath, privateKeyPath, passPhrase.toBytes(), outputFilePath); +``` + ## 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. From 365414329bd41d86b44312c717f4c4d3cecd0cc5 Mon Sep 17 00:00:00 2001 From: TharmiganK Date: Wed, 2 Oct 2024 16:23:31 +0530 Subject: [PATCH 10/42] Update to next minor version --- gradle.properties | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle.properties b/gradle.properties index ac5ea3e..66ab4c5 100644 --- a/gradle.properties +++ b/gradle.properties @@ -1,6 +1,6 @@ org.gradle.caching=true group=io.ballerina.stdlib -version=2.7.3-SNAPSHOT +version=2.8.0-SNAPSHOT puppycrawlCheckstyleVersion=10.12.0 bouncycastleVersion=1.78 githubSpotbugsVersion=5.0.14 From 1fa055274a5410bf41250c5437da43bd2554dee4 Mon Sep 17 00:00:00 2001 From: TharmiganK Date: Wed, 2 Oct 2024 16:23:55 +0530 Subject: [PATCH 11/42] [Automated] Update the native jar versions --- ballerina/Ballerina.toml | 6 +++--- ballerina/Dependencies.toml | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/ballerina/Ballerina.toml b/ballerina/Ballerina.toml index 74a87ba..1ce6ae2 100644 --- a/ballerina/Ballerina.toml +++ b/ballerina/Ballerina.toml @@ -1,7 +1,7 @@ [package] org = "ballerina" name = "crypto" -version = "2.7.3" +version = "2.8.0" authors = ["Ballerina"] keywords = ["security", "hash", "hmac", "sign", "encrypt", "decrypt", "private key", "public key"] repository = "https://github.com/ballerina-platform/module-ballerina-crypto" @@ -15,8 +15,8 @@ graalvmCompatible = true [[platform.java17.dependency]] groupId = "io.ballerina.stdlib" artifactId = "crypto-native" -version = "2.7.3" -path = "../native/build/libs/crypto-native-2.7.3-SNAPSHOT.jar" +version = "2.8.0" +path = "../native/build/libs/crypto-native-2.8.0-SNAPSHOT.jar" [[platform.java17.dependency]] groupId = "org.bouncycastle" diff --git a/ballerina/Dependencies.toml b/ballerina/Dependencies.toml index 52cab4f..3cc7bc4 100644 --- a/ballerina/Dependencies.toml +++ b/ballerina/Dependencies.toml @@ -10,7 +10,7 @@ distribution-version = "2201.9.0" [[package]] org = "ballerina" name = "crypto" -version = "2.7.3" +version = "2.8.0" dependencies = [ {org = "ballerina", name = "io"}, {org = "ballerina", name = "jballerina.java"}, From 95f34c14ff9a9aed77d3bacf790ad68be888de4a Mon Sep 17 00:00:00 2001 From: TharmiganK Date: Wed, 2 Oct 2024 16:31:26 +0530 Subject: [PATCH 12/42] [Automated] Update the native jar versions --- ballerina/Dependencies.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ballerina/Dependencies.toml b/ballerina/Dependencies.toml index 3cc7bc4..0d00631 100644 --- a/ballerina/Dependencies.toml +++ b/ballerina/Dependencies.toml @@ -107,7 +107,7 @@ modules = [ [[package]] org = "ballerina" name = "time" -version = "2.4.0" +version = "2.5.0" dependencies = [ {org = "ballerina", name = "jballerina.java"} ] From 957f623f15f96c143aa17ceb119ce2c4c7720bda Mon Sep 17 00:00:00 2001 From: TharmiganK Date: Wed, 2 Oct 2024 16:36:37 +0530 Subject: [PATCH 13/42] Add IO test dependency --- build.gradle | 2 ++ gradle.properties | 1 + 2 files changed, 3 insertions(+) diff --git a/build.gradle b/build.gradle index 6057360..be8428c 100644 --- a/build.gradle +++ b/build.gradle @@ -26,6 +26,7 @@ ext.puppycrawlCheckstyleVersion = project.puppycrawlCheckstyleVersion ext.bouncycastleVersion = project.bouncycastleVersion ext.ballerinaLangVersion = project.ballerinaLangVersion ext.stdlibTimeVersion = project.stdlibTimeVersion +ext.stdlibIoVersion = project.stdlibIoVersion allprojects { group = project.group @@ -68,6 +69,7 @@ subprojects { dependencies { /* Standard libraries */ ballerinaStdLibs "io.ballerina.stdlib:time-ballerina:${stdlibTimeVersion}" + ballerinaStdLibs "io.ballerina.stdlib:io-ballerina:${stdlibIoVersion}" } } diff --git a/gradle.properties b/gradle.properties index 66ab4c5..b8f845a 100644 --- a/gradle.properties +++ b/gradle.properties @@ -12,3 +12,4 @@ nativeImageVersion=22.2.0 ballerinaLangVersion=2201.9.0 stdlibTimeVersion=2.4.0 +stdlibIoVersion=1.6.1 From 5ba88d7d21748a54d11ba4f7eda224128b2b288c Mon Sep 17 00:00:00 2001 From: TharmiganK Date: Wed, 2 Oct 2024 16:37:15 +0530 Subject: [PATCH 14/42] Add suggestions from review --- ballerina/encrypt_decrypt.bal | 6 +-- ballerina/tests/encrypt_decrypt_pgp_test.bal | 2 +- ballerina/tests/test_utils.bal | 50 +++++++++---------- .../stdlib/crypto/nativeimpl/Decrypt.java | 3 +- 4 files changed, 31 insertions(+), 30 deletions(-) diff --git a/ballerina/encrypt_decrypt.bal b/ballerina/encrypt_decrypt.bal index f2f7058..26b04db 100644 --- a/ballerina/encrypt_decrypt.bal +++ b/ballerina/encrypt_decrypt.bal @@ -260,7 +260,8 @@ public isolated function encryptPgp(byte[] plainText, string publicKeyPath, *Opt 'class: "io.ballerina.stdlib.crypto.nativeimpl.Encrypt" } external; -# Writes the PGP-encrypted value of the given data to a file specified by the output file path. +# Writes the PGP-encrypted value of the content given in the input file to a file specified by the output file path. +# If the output file already exists, it will be overwritten. # ```ballerina # check crypto:encryptPgpAsFile("input.txt", "public_key.asc", "output.txt"); # ``` @@ -276,7 +277,6 @@ public isolated function encryptPgpAsFile(string inputFilePath, string publicKey } external; # Returns the PGP-decrypted value of the given PGP-encrypted data. -# If the output file already exists, it will be overwritten. # ```ballerina # byte[] message = "Hello Ballerina!".toBytes(); # byte[] cipherText = check crypto:encryptPgp(message, "public_key.asc"); @@ -295,7 +295,7 @@ public isolated function decryptPgp(byte[] cipherText, string privateKeyPath, by 'class: "io.ballerina.stdlib.crypto.nativeimpl.Decrypt" } external; -# Writes the PGP-decrypted value of the given data to a file specified by the output file path. +# Writes the PGP-decrypted value of the content given in the input file to a file specified by the output file path. # If the output file already exists, it will be overwritten. # ```ballerina # byte[] passphrase = check io:fileReadBytes("pass_phrase.txt"); diff --git a/ballerina/tests/encrypt_decrypt_pgp_test.bal b/ballerina/tests/encrypt_decrypt_pgp_test.bal index 686dd54..885de76 100644 --- a/ballerina/tests/encrypt_decrypt_pgp_test.bal +++ b/ballerina/tests/encrypt_decrypt_pgp_test.bal @@ -114,5 +114,5 @@ isolated function testNegativeEncryptAndDecryptFileWithPgpInvalidPassphrase() re isolated function isSameFileContent(string inputFilePath, string outputFilePath) returns boolean|error { byte[] input = check io:fileReadBytes(inputFilePath); byte[] output = check io:fileReadBytes(outputFilePath); - return input.toBase64() == output.toBase64(); + return input == output; } diff --git a/ballerina/tests/test_utils.bal b/ballerina/tests/test_utils.bal index 53655ae..8f1a9a0 100644 --- a/ballerina/tests/test_utils.bal +++ b/ballerina/tests/test_utils.bal @@ -14,31 +14,31 @@ // specific language governing permissions and limitations // under the License. -const string KEYSTORE_PATH = "tests/resources/keyStore.p12"; -const string EC_KEYSTORE_PATH = "tests/resources/ec-keystore.pkcs12"; -const string MLDSA_KEYSTORE_PATH = "tests/resources/mldsa-keystore.pkcs12"; -const string MLKEM_KEYSTORE_PATH = "tests/resources/mlkem-keystore.pkcs12"; -const string ENCRYPTED_KEY_PAIR_PATH = "tests/resources/encryptedKeyPair.pem"; -const string KEY_PAIR_PATH = "tests/resources/keyPair.pem"; -const string ENCRYPTED_PRIVATE_KEY_PATH = "tests/resources/encryptedPrivate.key"; -const string PRIVATE_KEY_PATH = "tests/resources/private.key"; -const string X509_PUBLIC_CERT_PATH = "tests/resources/public.crt"; -const string EC_CERT_PATH = "tests/resources/ec-cert.crt"; -const string EC_PRIVATE_KEY_PATH = "tests/resources/ec-key.pem"; -const string MLDSA_CERT_PATH = "tests/resources/mldsa-cert.crt"; -const string MLDSA_PRIVATE_KEY_PATH = "tests/resources/mldsa-key.pem"; -const string MLKEM_CERT_PATH = "tests/resources/mlkem-cert.crt"; -const string MLKEM_PRIVATE_KEY_PATH = "tests/resources/mlkem-key.pem"; +const KEYSTORE_PATH = "tests/resources/keyStore.p12"; +const EC_KEYSTORE_PATH = "tests/resources/ec-keystore.pkcs12"; +const MLDSA_KEYSTORE_PATH = "tests/resources/mldsa-keystore.pkcs12"; +const MLKEM_KEYSTORE_PATH = "tests/resources/mlkem-keystore.pkcs12"; +const ENCRYPTED_KEY_PAIR_PATH = "tests/resources/encryptedKeyPair.pem"; +const KEY_PAIR_PATH = "tests/resources/keyPair.pem"; +const ENCRYPTED_PRIVATE_KEY_PATH = "tests/resources/encryptedPrivate.key"; +const PRIVATE_KEY_PATH = "tests/resources/private.key"; +const X509_PUBLIC_CERT_PATH = "tests/resources/public.crt"; +const EC_CERT_PATH = "tests/resources/ec-cert.crt"; +const EC_PRIVATE_KEY_PATH = "tests/resources/ec-key.pem"; +const MLDSA_CERT_PATH = "tests/resources/mldsa-cert.crt"; +const MLDSA_PRIVATE_KEY_PATH = "tests/resources/mldsa-key.pem"; +const MLKEM_CERT_PATH = "tests/resources/mlkem-cert.crt"; +const MLKEM_PRIVATE_KEY_PATH = "tests/resources/mlkem-key.pem"; -const string INVALID_KEYSTORE_PATH = "tests/resources/cert/keyStore.p12.invalid"; -const string INVALID_PRIVATE_KEY_PATH = "tests/resources/cert/private.key.invalid"; -const string INVALID_PUBLIC_CERT_PATH = "tests/resources/cert/public.crt.invalid"; +const INVALID_KEYSTORE_PATH = "tests/resources/cert/keyStore.p12.invalid"; +const INVALID_PRIVATE_KEY_PATH = "tests/resources/cert/private.key.invalid"; +const INVALID_PUBLIC_CERT_PATH = "tests/resources/cert/public.crt.invalid"; -const string PGP_PUBLIC_KEY_PATH = "tests/resources/public_key.asc"; -const string PGP_PRIVATE_KEY_PATH = "tests/resources/private_key.asc"; -const string PGP_INVALID_PRIVATE_KEY_PATH = "tests/resources/invalid_private_key.asc"; -const string PGP_PRIVATE_KEY_PASSPHRASE_PATH = "tests/resources/pgp_private_key_passphrase.txt"; +const PGP_PUBLIC_KEY_PATH = "tests/resources/public_key.asc"; +const PGP_PRIVATE_KEY_PATH = "tests/resources/private_key.asc"; +const PGP_INVALID_PRIVATE_KEY_PATH = "tests/resources/invalid_private_key.asc"; +const PGP_PRIVATE_KEY_PASSPHRASE_PATH = "tests/resources/pgp_private_key_passphrase.txt"; -const string SAMPLE_TEXT = "tests/resources/sample.txt"; -const string TARGET_ENCRYPTION_OUTPUT = "target/encrypted_output.txt"; -const string TARGET_DECRYPTION_OUTPUT = "target/decrypted_output.txt"; +const SAMPLE_TEXT = "tests/resources/sample.txt"; +const TARGET_ENCRYPTION_OUTPUT = "target/encrypted_output.txt"; +const TARGET_DECRYPTION_OUTPUT = "target/decrypted_output.txt"; diff --git a/native/src/main/java/io/ballerina/stdlib/crypto/nativeimpl/Decrypt.java b/native/src/main/java/io/ballerina/stdlib/crypto/nativeimpl/Decrypt.java index 82914fe..21373c1 100644 --- a/native/src/main/java/io/ballerina/stdlib/crypto/nativeimpl/Decrypt.java +++ b/native/src/main/java/io/ballerina/stdlib/crypto/nativeimpl/Decrypt.java @@ -112,7 +112,8 @@ public static Object decryptPgpAsFile(BString inputFilePath, BString privateKeyP } try (InputStream keyStream = new ByteArrayInputStream(privateKey); - InputStream cipherTextStream = Files.newInputStream(Path.of(inputFilePath.toString()))) { + InputStream cipherTextStream = Files.newInputStream(Path.of(inputFilePath.toString())) + ) { PgpDecryptionGenerator pgpDecryptionGenerator = new PgpDecryptionGenerator(keyStream, passphraseInBytes); pgpDecryptionGenerator.decrypt(cipherTextStream, outputFilePath.getValue()); return null; From e207f957cb284d4252637b175a508fda6e2e9406 Mon Sep 17 00:00:00 2001 From: TharmiganK Date: Wed, 2 Oct 2024 16:39:27 +0530 Subject: [PATCH 15/42] Change error message --- ballerina/tests/encrypt_decrypt_pgp_test.bal | 4 ++-- .../io/ballerina/stdlib/crypto/PgpDecryptionGenerator.java | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/ballerina/tests/encrypt_decrypt_pgp_test.bal b/ballerina/tests/encrypt_decrypt_pgp_test.bal index 885de76..562ebe1 100644 --- a/ballerina/tests/encrypt_decrypt_pgp_test.bal +++ b/ballerina/tests/encrypt_decrypt_pgp_test.bal @@ -42,7 +42,7 @@ isolated function testNegativeEncryptAndDecryptWithPgpInvalidPrivateKey() return byte[] cipherText = check encryptPgp(message, PGP_PUBLIC_KEY_PATH); byte[]|Error plainText = decryptPgp(cipherText, PGP_INVALID_PRIVATE_KEY_PATH, passphrase); if plainText is Error { - test:assertEquals(plainText.message(), "Error occurred while PGP decrypt: Could Not Extract private key"); + test:assertEquals(plainText.message(), "Error occurred while PGP decrypt: Could not Extract private key"); } else { test:assertFail("Should return a crypto Error"); } @@ -90,7 +90,7 @@ isolated function testNegativeEncryptAndDecryptFileWithPgpInvalidPrivateKey() re check encryptPgpAsFile(SAMPLE_TEXT, PGP_PUBLIC_KEY_PATH, TARGET_ENCRYPTION_OUTPUT); error? err = decryptPgpAsFile(TARGET_ENCRYPTION_OUTPUT, PGP_INVALID_PRIVATE_KEY_PATH, passphrase, TARGET_DECRYPTION_OUTPUT); if err is Error { - test:assertEquals(err.message(), "Error occurred while PGP decrypt: Could Not Extract private key"); + test:assertEquals(err.message(), "Error occurred while PGP decrypt: Could not Extract private key"); } else { test:assertFail("Should return a crypto Error"); } diff --git a/native/src/main/java/io/ballerina/stdlib/crypto/PgpDecryptionGenerator.java b/native/src/main/java/io/ballerina/stdlib/crypto/PgpDecryptionGenerator.java index 1cba6a8..286f2a4 100644 --- a/native/src/main/java/io/ballerina/stdlib/crypto/PgpDecryptionGenerator.java +++ b/native/src/main/java/io/ballerina/stdlib/crypto/PgpDecryptionGenerator.java @@ -111,7 +111,7 @@ private void decryptStream(InputStream encryptedIn, OutputStream clearOut) } if (pgpPrivateKey.isEmpty()) { - throw new PGPException("Could Not Extract private key"); + throw new PGPException("Could not Extract private key"); } decrypt(clearOut, pgpPrivateKey, publicKeyEncryptedData); } From 18ff4cc0aa5ac399aa04b48c794bc01ca6f7da57 Mon Sep 17 00:00:00 2001 From: TharmiganK Date: Wed, 2 Oct 2024 16:40:54 +0530 Subject: [PATCH 16/42] Update time version --- ballerina/Dependencies.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ballerina/Dependencies.toml b/ballerina/Dependencies.toml index 0d00631..3cc7bc4 100644 --- a/ballerina/Dependencies.toml +++ b/ballerina/Dependencies.toml @@ -107,7 +107,7 @@ modules = [ [[package]] org = "ballerina" name = "time" -version = "2.5.0" +version = "2.4.0" dependencies = [ {org = "ballerina", name = "jballerina.java"} ] From 95c6beb2aee1f55006892ff830f885d6146e4413 Mon Sep 17 00:00:00 2001 From: TharmiganK Date: Mon, 7 Oct 2024 12:55:49 +0530 Subject: [PATCH 17/42] Add stream pgp decrypt support --- ballerina/encrypt_decrypt.bal | 24 +++- ballerina/stream_iterator.bal | 33 +++++ .../stdlib/crypto/BallerinaInputStream.java | 120 ++++++++++++++++++ .../io/ballerina/stdlib/crypto/Constants.java | 4 + .../stdlib/crypto/PgpDecryptionGenerator.java | 118 +++++++++++++---- .../stdlib/crypto/nativeimpl/Decrypt.java | 31 +++++ .../stdlib/crypto/nativeimpl/StreamUtils.java | 84 ++++++++++++ spotbugs-exclude.xml | 5 +- 8 files changed, 388 insertions(+), 31 deletions(-) create mode 100644 ballerina/stream_iterator.bal create mode 100644 native/src/main/java/io/ballerina/stdlib/crypto/BallerinaInputStream.java create mode 100644 native/src/main/java/io/ballerina/stdlib/crypto/nativeimpl/StreamUtils.java diff --git a/ballerina/encrypt_decrypt.bal b/ballerina/encrypt_decrypt.bal index 26b04db..6e1e446 100644 --- a/ballerina/encrypt_decrypt.bal +++ b/ballerina/encrypt_decrypt.bal @@ -111,7 +111,7 @@ public isolated function encryptAesCbc(byte[] input, byte[] key, byte[] iv, AesP # + padding - The padding algorithm # + return - Encrypted data or else a `crypto:Error` if the key is invalid public isolated function encryptAesEcb(byte[] input, byte[] key, AesPadding padding = PKCS5) - returns byte[]|Error = @java:Method { + returns byte[]|Error = @java:Method { name: "encryptAesEcb", 'class: "io.ballerina.stdlib.crypto.nativeimpl.Encrypt" } external; @@ -189,7 +189,7 @@ public isolated function decryptRsaEcb(byte[] input, PrivateKey|PublicKey key, R # + padding - The padding algorithm # + return - Decrypted data or else a `crypto:Error` if the key is invalid public isolated function decryptAesCbc(byte[] input, byte[] key, byte[] iv, AesPadding padding = PKCS5) - returns byte[]|Error = @java:Method { + returns byte[]|Error = @java:Method { name: "decryptAesCbc", 'class: "io.ballerina.stdlib.crypto.nativeimpl.Decrypt" } external; @@ -255,7 +255,7 @@ public isolated function decryptAesGcm(byte[] input, byte[] key, byte[] iv, AesP # + options - PGP encryption options # + return - Encrypted data or else a `crypto:Error` if the key is invalid public isolated function encryptPgp(byte[] plainText, string publicKeyPath, *Options options) - returns byte[]|Error = @java:Method { + returns byte[]|Error = @java:Method { name: "encryptPgp", 'class: "io.ballerina.stdlib.crypto.nativeimpl.Encrypt" } external; @@ -280,7 +280,7 @@ public isolated function encryptPgpAsFile(string inputFilePath, string publicKey # ```ballerina # byte[] message = "Hello Ballerina!".toBytes(); # byte[] cipherText = check crypto:encryptPgp(message, "public_key.asc"); -# +# # byte[] passphrase = check io:fileReadBytes("pass_phrase.txt"); # byte[] decryptedMessage = check crypto:decryptPgp(cipherText, "private_key.asc", passphrase); # ``` @@ -311,3 +311,19 @@ public isolated function decryptPgpAsFile(string inputFilePath, string privateKe string outputFilePath) returns Error? = @java:Method { 'class: "io.ballerina.stdlib.crypto.nativeimpl.Decrypt" } external; + +# Returns the PGP-decrypted stream of the content given in the input stream. +# ```ballerina +# byte[] passphrase = check io:fileReadBytes("pass_phrase.txt"); +# stream inputStream = check io:fileReadBlocksAsStream("pgb_encrypted.txt"); +# stream|Error decryptedStream = crypto:decryptStreamPgp(inputStream, "private_key.asc", passphrase); +# ``` +# +# + inputStream - The encrypted content as a stream +# + privateKeyPath - Path to the private key +# + passphrase - passphrase of the private key +# + return - Decrypted stream or else a `crypto:Error` if the key or passphrase is invalid +public isolated function decryptStreamPgp(stream inputStream, string privateKeyPath, + byte[] passphrase) returns stream|Error = @java:Method { + 'class: "io.ballerina.stdlib.crypto.nativeimpl.Decrypt" +} external; diff --git a/ballerina/stream_iterator.bal b/ballerina/stream_iterator.bal new file mode 100644 index 0000000..2ca2cf9 --- /dev/null +++ b/ballerina/stream_iterator.bal @@ -0,0 +1,33 @@ +import ballerina/jballerina.java; + +class StreamIterator { + boolean isClosed = false; + + isolated function next() returns record {|byte[] value;|}|Error? { + byte[]|Error? bytes = self.read(); + if bytes is byte[] { + return {value: bytes}; + } else { + return bytes; + } + } + + isolated function close() returns Error? { + if !self.isClosed { + var closeResult = self.closeStream(); + if closeResult is () { + self.isClosed = true; + } + return closeResult; + } + return; + } + + isolated function read() returns byte[]|Error? = @java:Method { + 'class: "io.ballerina.stdlib.crypto.nativeimpl.StreamUtils" + } external; + + isolated function closeStream() returns Error? = @java:Method { + 'class: "io.ballerina.stdlib.crypto.nativeimpl.StreamUtils" + } external; +} diff --git a/native/src/main/java/io/ballerina/stdlib/crypto/BallerinaInputStream.java b/native/src/main/java/io/ballerina/stdlib/crypto/BallerinaInputStream.java new file mode 100644 index 0000000..368b72e --- /dev/null +++ b/native/src/main/java/io/ballerina/stdlib/crypto/BallerinaInputStream.java @@ -0,0 +1,120 @@ +/* + * Copyright (c) 2024 WSO2 LLC. (https://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; + +import io.ballerina.runtime.api.Environment; +import io.ballerina.runtime.api.async.Callback; +import io.ballerina.runtime.api.utils.StringUtils; +import io.ballerina.runtime.api.values.BArray; +import io.ballerina.runtime.api.values.BError; +import io.ballerina.runtime.api.values.BMap; +import io.ballerina.runtime.api.values.BStream; + +import java.io.IOException; +import java.io.InputStream; +import java.nio.ByteBuffer; +import java.util.Objects; +import java.util.concurrent.CountDownLatch; + +/** + * Represents a Ballerina stream as an {@link InputStream}. + * + * @since 2.8.0 + */ +public class BallerinaInputStream extends InputStream { + private final Environment environment; + private final BStream ballerinaStream; + private ByteBuffer buffer = null; + + public BallerinaInputStream(Environment environment, BStream ballerinaStream) { + this.ballerinaStream = ballerinaStream; + this.environment = environment; + } + + @Override + public int read() throws IOException { + if (Objects.isNull(buffer) || !buffer.hasRemaining()) { + Object nextElement = getNext(); + if (nextElement instanceof BError) { + throw new IOException(((BError) nextElement).getMessage()); + } + if (Objects.isNull(nextElement)) { + return -1; + } + if (nextElement instanceof BMap nextValue) { + Object nextBytes = nextValue.get(StringUtils.fromString("value")); + if (nextBytes instanceof BArray) { + buffer = ByteBuffer.wrap(((BArray) nextBytes).getBytes()); + } else { + throw new IOException("Error occurred while reading the next element from the stream: " + + "unexpected value type"); + } + } else { + throw new IOException("Error occurred while reading the next element from the stream: " + + "unexpected value type"); + } + } + return buffer.get() & 0xFF; + } + + @Override + public void close() { + Object result = callBallerinaFunction("close", "Error occurred while closing the stream"); + if (result instanceof BError) { + throw new RuntimeException(((BError) result).getMessage()); + } + } + + public Object getNext() { + return callBallerinaFunction("next", "Error occurred while reading the next element from the stream"); + } + + private Object callBallerinaFunction(String functionName, String message) { + final Object[] nextResult = new Object[1]; + CountDownLatch countDownLatch = new CountDownLatch(1); + Callback returnCallback = new StreamCallback(message, nextResult, countDownLatch); + + environment.getRuntime().invokeMethodAsyncSequentially(ballerinaStream.getIteratorObj(), functionName, null, + null, returnCallback, null, null); + try { + countDownLatch.await(); + } catch (InterruptedException exception) { + return CryptoUtils.createError("Error occurred while reading the next element from the stream: " + + "interrupted exception"); + } + return nextResult[0]; + } + + private record StreamCallback(String message, Object[] nextResult, + CountDownLatch countDownLatch) implements Callback { + + @Override + public void notifySuccess(Object result) { + nextResult[0] = result; + countDownLatch.countDown(); + } + + @Override + public void notifyFailure(BError bError) { + BError error = CryptoUtils.createError(String.format("%s: %s", message, bError.getMessage())); + nextResult[0] = error; + countDownLatch.countDown(); + } + } +} + diff --git a/native/src/main/java/io/ballerina/stdlib/crypto/Constants.java b/native/src/main/java/io/ballerina/stdlib/crypto/Constants.java index 52ffec4..b084fca 100644 --- a/native/src/main/java/io/ballerina/stdlib/crypto/Constants.java +++ b/native/src/main/java/io/ballerina/stdlib/crypto/Constants.java @@ -124,4 +124,8 @@ private Constants() {} public static final String GCM = "GCM"; public static final String AES = "AES"; public static final String RSA = "RSA"; + + public static final String DECRYPTED_STREAM = "INPUT_STREAM"; + public static final String COMPRESSED_PGP_STREAM = "COMPRESSED_PGP_STREAM"; + public static final String COMPRESSED_STREAM = "COMPRESSED_STREAM"; } diff --git a/native/src/main/java/io/ballerina/stdlib/crypto/PgpDecryptionGenerator.java b/native/src/main/java/io/ballerina/stdlib/crypto/PgpDecryptionGenerator.java index 286f2a4..a4fe3c9 100644 --- a/native/src/main/java/io/ballerina/stdlib/crypto/PgpDecryptionGenerator.java +++ b/native/src/main/java/io/ballerina/stdlib/crypto/PgpDecryptionGenerator.java @@ -18,6 +18,7 @@ package io.ballerina.stdlib.crypto; import io.ballerina.runtime.api.creators.ValueCreator; +import io.ballerina.runtime.api.values.BObject; import org.bouncycastle.jce.provider.BouncyCastleProvider; import org.bouncycastle.openpgp.PGPCompressedData; import org.bouncycastle.openpgp.PGPEncryptedData; @@ -50,6 +51,10 @@ import java.util.Objects; import java.util.Optional; +import static io.ballerina.stdlib.crypto.Constants.COMPRESSED_PGP_STREAM; +import static io.ballerina.stdlib.crypto.Constants.COMPRESSED_STREAM; +import static io.ballerina.stdlib.crypto.Constants.DECRYPTED_STREAM; + /** * Provides functionality for PGP decryption operations. * @@ -113,7 +118,36 @@ private void decryptStream(InputStream encryptedIn, OutputStream clearOut) if (pgpPrivateKey.isEmpty()) { throw new PGPException("Could not Extract private key"); } - decrypt(clearOut, pgpPrivateKey, publicKeyEncryptedData); + decrypt(clearOut, pgpPrivateKey.get(), publicKeyEncryptedData); + } + + private void decryptStream(InputStream encryptedIn, BObject iteratorObj) throws PGPException, IOException { + // Remove armour and return the underlying binary encrypted stream + encryptedIn = PGPUtil.getDecoderStream(encryptedIn); + JcaPGPObjectFactory pgpObjectFactory = new JcaPGPObjectFactory(encryptedIn); + + Object obj = pgpObjectFactory.nextObject(); + // Verify the marker packet + PGPEncryptedDataList pgpEncryptedDataList = (obj instanceof PGPEncryptedDataList) + ? (PGPEncryptedDataList) obj : (PGPEncryptedDataList) pgpObjectFactory.nextObject(); + + Optional pgpPrivateKey = Optional.empty(); + PGPPublicKeyEncryptedData publicKeyEncryptedData = null; + + Iterator encryptedDataItr = pgpEncryptedDataList.getEncryptedDataObjects(); + while (pgpPrivateKey.isEmpty() && encryptedDataItr.hasNext()) { + publicKeyEncryptedData = (PGPPublicKeyEncryptedData) encryptedDataItr.next(); + pgpPrivateKey = findSecretKey(publicKeyEncryptedData.getKeyID()); + } + + if (Objects.isNull(publicKeyEncryptedData)) { + throw new PGPException("Could not generate PGPPublicKeyEncryptedData object"); + } + + if (pgpPrivateKey.isEmpty()) { + throw new PGPException("Could not Extract private key"); + } + decrypt(pgpPrivateKey.get(), publicKeyEncryptedData, iteratorObj); } // Decrypts the given byte array of encrypted data using PGP decryption. @@ -125,48 +159,80 @@ public Object decrypt(byte[] encryptedBytes) throws PGPException, IOException { } } + public void decrypt(InputStream encryptedIn, BObject iteratorObj) throws PGPException, IOException { + decryptStream(encryptedIn, iteratorObj); + } + public void decrypt(InputStream encryptedIn, String outputPath) throws PGPException, IOException { try (OutputStream outputStream = Files.newOutputStream(Path.of(outputPath))) { decryptStream(encryptedIn, outputStream); } } - private static void decrypt(OutputStream clearOut, Optional pgpPrivateKey, + private static void decrypt(OutputStream clearOut, PGPPrivateKey pgpPrivateKey, PGPPublicKeyEncryptedData publicKeyEncryptedData) throws IOException, PGPException { - if (pgpPrivateKey.isPresent()) { - PublicKeyDataDecryptorFactory decryptorFactory = new JcePublicKeyDataDecryptorFactoryBuilder() - .setProvider(BouncyCastleProvider.PROVIDER_NAME).build(pgpPrivateKey.get()); - try (InputStream decryptedCompressedIn = publicKeyEncryptedData.getDataStream(decryptorFactory)) { - - JcaPGPObjectFactory decCompObjFac = new JcaPGPObjectFactory(decryptedCompressedIn); - PGPCompressedData pgpCompressedData = (PGPCompressedData) decCompObjFac.nextObject(); - - try (InputStream compressedDataStream = new BufferedInputStream(pgpCompressedData.getDataStream())) { - JcaPGPObjectFactory pgpCompObjFac = new JcaPGPObjectFactory(compressedDataStream); - - Object message = pgpCompObjFac.nextObject(); - - if (message instanceof PGPLiteralData pgpLiteralData) { - try (InputStream decDataStream = pgpLiteralData.getInputStream()) { - byte[] buffer = new byte[1024]; - int bytesRead; - while ((bytesRead = decDataStream.read(buffer)) != -1) { - clearOut.write(buffer, 0, bytesRead); - } + PublicKeyDataDecryptorFactory decryptorFactory = new JcePublicKeyDataDecryptorFactoryBuilder() + .setProvider(BouncyCastleProvider.PROVIDER_NAME).build(pgpPrivateKey); + try (InputStream decryptedCompressedIn = publicKeyEncryptedData.getDataStream(decryptorFactory)) { + + JcaPGPObjectFactory decCompObjFac = new JcaPGPObjectFactory(decryptedCompressedIn); + PGPCompressedData pgpCompressedData = (PGPCompressedData) decCompObjFac.nextObject(); + + try (InputStream compressedDataStream = new BufferedInputStream(pgpCompressedData.getDataStream())) { + JcaPGPObjectFactory pgpCompObjFac = new JcaPGPObjectFactory(compressedDataStream); + + Object message = pgpCompObjFac.nextObject(); + + if (message instanceof PGPLiteralData pgpLiteralData) { + try (InputStream decDataStream = pgpLiteralData.getInputStream()) { + byte[] buffer = new byte[1024]; + int bytesRead; + while ((bytesRead = decDataStream.read(buffer)) != -1) { + clearOut.write(buffer, 0, bytesRead); } - } else if (message instanceof PGPOnePassSignatureList) { - throw new PGPException("Encrypted message contains a signed message not literal data"); - } else { - throw new PGPException("Unknown message type encountered during decryption"); } + } else if (message instanceof PGPOnePassSignatureList) { + throw new PGPException("Encrypted message contains a signed message not literal data"); + } else { + throw new PGPException("Unknown message type encountered during decryption"); } } + } + // Perform the integrity check + if (publicKeyEncryptedData.isIntegrityProtected()) { + if (!publicKeyEncryptedData.verify()) { + throw new PGPException("Message failed integrity check"); + } + } + } + + private static void decrypt(PGPPrivateKey pgpPrivateKey, PGPPublicKeyEncryptedData publicKeyEncryptedData, + BObject iteratorObj) throws IOException, PGPException { + PublicKeyDataDecryptorFactory decryptorFactory = new JcePublicKeyDataDecryptorFactoryBuilder() + .setProvider(BouncyCastleProvider.PROVIDER_NAME).build(pgpPrivateKey); + InputStream decryptedCompressedIn = publicKeyEncryptedData.getDataStream(decryptorFactory); + JcaPGPObjectFactory decCompObjFac = new JcaPGPObjectFactory(decryptedCompressedIn); + PGPCompressedData pgpCompressedData = (PGPCompressedData) decCompObjFac.nextObject(); + + InputStream compressedDataStream = new BufferedInputStream(pgpCompressedData.getDataStream()); + JcaPGPObjectFactory pgpCompObjFac = new JcaPGPObjectFactory(compressedDataStream); + + Object message = pgpCompObjFac.nextObject(); + + if (message instanceof PGPLiteralData pgpLiteralData) { // Perform the integrity check if (publicKeyEncryptedData.isIntegrityProtected()) { if (!publicKeyEncryptedData.verify()) { throw new PGPException("Message failed integrity check"); } } + iteratorObj.addNativeData(DECRYPTED_STREAM, pgpLiteralData.getDataStream()); + iteratorObj.addNativeData(COMPRESSED_PGP_STREAM, compressedDataStream); + iteratorObj.addNativeData(COMPRESSED_STREAM, decryptedCompressedIn); + } else if (message instanceof PGPOnePassSignatureList) { + throw new PGPException("Encrypted message contains a signed message not literal data"); + } else { + throw new PGPException("Unknown message type encountered during decryption"); } } } diff --git a/native/src/main/java/io/ballerina/stdlib/crypto/nativeimpl/Decrypt.java b/native/src/main/java/io/ballerina/stdlib/crypto/nativeimpl/Decrypt.java index 21373c1..03baebb 100644 --- a/native/src/main/java/io/ballerina/stdlib/crypto/nativeimpl/Decrypt.java +++ b/native/src/main/java/io/ballerina/stdlib/crypto/nativeimpl/Decrypt.java @@ -18,9 +18,17 @@ package io.ballerina.stdlib.crypto.nativeimpl; +import io.ballerina.runtime.api.Environment; +import io.ballerina.runtime.api.PredefinedTypes; +import io.ballerina.runtime.api.creators.TypeCreator; +import io.ballerina.runtime.api.creators.ValueCreator; +import io.ballerina.runtime.api.types.Type; import io.ballerina.runtime.api.values.BArray; import io.ballerina.runtime.api.values.BMap; +import io.ballerina.runtime.api.values.BObject; +import io.ballerina.runtime.api.values.BStream; import io.ballerina.runtime.api.values.BString; +import io.ballerina.stdlib.crypto.BallerinaInputStream; import io.ballerina.stdlib.crypto.Constants; import io.ballerina.stdlib.crypto.CryptoUtils; import io.ballerina.stdlib.crypto.PgpDecryptionGenerator; @@ -121,4 +129,27 @@ public static Object decryptPgpAsFile(BString inputFilePath, BString privateKeyP return CryptoUtils.createError("Error occurred while PGP decrypt: " + e.getMessage()); } } + + public static Object decryptStreamPgp(Environment environment, BStream inputStream, BString privateKeyPath, + BArray passphrase) { + byte[] passphraseInBytes = passphrase.getBytes(); + byte[] privateKey; + try { + privateKey = Files.readAllBytes(Path.of(privateKeyPath.toString())); + } catch (IOException e) { + return CryptoUtils.createError("Error occurred while reading private key: " + e.getMessage()); + } + + try (InputStream keyStream = new ByteArrayInputStream(privateKey)) { + InputStream cipherTextStream = new BallerinaInputStream(environment, inputStream); + PgpDecryptionGenerator pgpDecryptionGenerator = new PgpDecryptionGenerator(keyStream, passphraseInBytes); + BObject iteratorObj = ValueCreator.createObjectValue(ModuleUtils.getModule(), "StreamIterator"); + pgpDecryptionGenerator.decrypt(cipherTextStream, iteratorObj); + Type constrainedType = TypeCreator.createArrayType(PredefinedTypes.TYPE_BYTE); + return ValueCreator.createStreamValue(TypeCreator.createStreamType(constrainedType), + iteratorObj); + } catch (IOException | PGPException e) { + return CryptoUtils.createError("Error occurred while PGP decrypt: " + e.getMessage()); + } + } } diff --git a/native/src/main/java/io/ballerina/stdlib/crypto/nativeimpl/StreamUtils.java b/native/src/main/java/io/ballerina/stdlib/crypto/nativeimpl/StreamUtils.java new file mode 100644 index 0000000..0949048 --- /dev/null +++ b/native/src/main/java/io/ballerina/stdlib/crypto/nativeimpl/StreamUtils.java @@ -0,0 +1,84 @@ +/* + * Copyright (c) 2024 WSO2 LLC. (https://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.nativeimpl; + +import io.ballerina.runtime.api.creators.ValueCreator; +import io.ballerina.runtime.api.values.BObject; +import io.ballerina.stdlib.crypto.CryptoUtils; + +import java.io.IOException; +import java.io.InputStream; +import java.util.Objects; + +import static io.ballerina.stdlib.crypto.Constants.COMPRESSED_PGP_STREAM; +import static io.ballerina.stdlib.crypto.Constants.COMPRESSED_STREAM; +import static io.ballerina.stdlib.crypto.Constants.DECRYPTED_STREAM; + +/** + * Provides functionality for stream operations. + * + * @since 2.8.0 + */ +public final class StreamUtils { + + private StreamUtils() { + } + + public static Object read(BObject iterator) { + Object stream = iterator.getNativeData(DECRYPTED_STREAM); + if (Objects.isNull(stream) || !(stream instanceof InputStream inputStream)) { + return CryptoUtils.createError("Stream is not available"); + } + try { + byte[] buffer = new byte[4096]; + int in = inputStream.read(buffer); + if (in == -1) { + return null; + } + if (in < buffer.length) { + byte[] temp = new byte[in]; + System.arraycopy(buffer, 0, temp, 0, in); + return ValueCreator.createArrayValue(temp); + } + return ValueCreator.createArrayValue(buffer); + } catch (IOException e) { + return CryptoUtils.createError("Error occurred while reading from the stream: " + e.getMessage()); + } + } + + public static Object closeStream(BObject iterator) { + Object result = closeNativeStream(iterator, DECRYPTED_STREAM); + // Ignore the errors occurred while closing the compressed streams. + closeNativeStream(iterator, COMPRESSED_PGP_STREAM); + closeNativeStream(iterator, COMPRESSED_STREAM); + return result; + } + + public static Object closeNativeStream(BObject iterator, String streamName) { + Object stream = iterator.getNativeData(streamName); + if (Objects.isNull(stream) || !(stream instanceof InputStream inputStream)) { + return CryptoUtils.createError("Stream is not available"); + } + try { + inputStream.close(); + } catch (IOException e) { + return CryptoUtils.createError("Error occurred while closing the stream: " + e.getMessage()); + } + return null; + } +} diff --git a/spotbugs-exclude.xml b/spotbugs-exclude.xml index e72d98d..8cf90ac 100644 --- a/spotbugs-exclude.xml +++ b/spotbugs-exclude.xml @@ -16,5 +16,8 @@ ~ under the License. --> - + + + + From 7c584bb8a0af41131019deacf13f76446d18ac14 Mon Sep 17 00:00:00 2001 From: TharmiganK Date: Mon, 7 Oct 2024 13:28:37 +0530 Subject: [PATCH 18/42] Fix casting issue --- ballerina/stream_iterator.bal | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/ballerina/stream_iterator.bal b/ballerina/stream_iterator.bal index 2ca2cf9..cd36c28 100644 --- a/ballerina/stream_iterator.bal +++ b/ballerina/stream_iterator.bal @@ -3,7 +3,7 @@ import ballerina/jballerina.java; class StreamIterator { boolean isClosed = false; - isolated function next() returns record {|byte[] value;|}|Error? { + public isolated function next() returns record {|byte[] value;|}|Error? { byte[]|Error? bytes = self.read(); if bytes is byte[] { return {value: bytes}; @@ -12,7 +12,7 @@ class StreamIterator { } } - isolated function close() returns Error? { + public isolated function close() returns Error? { if !self.isClosed { var closeResult = self.closeStream(); if closeResult is () { From d09e23548dc67fc52a07cf2baea6ba39263911c8 Mon Sep 17 00:00:00 2001 From: TharmiganK Date: Tue, 8 Oct 2024 11:21:47 +0530 Subject: [PATCH 19/42] [Automated] Update the native jar versions --- ballerina/Ballerina.toml | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/ballerina/Ballerina.toml b/ballerina/Ballerina.toml index 1ce6ae2..c0f9b2b 100644 --- a/ballerina/Ballerina.toml +++ b/ballerina/Ballerina.toml @@ -21,23 +21,23 @@ path = "../native/build/libs/crypto-native-2.8.0-SNAPSHOT.jar" [[platform.java17.dependency]] groupId = "org.bouncycastle" artifactId = "bcpkix-jdk18on" -version = "1.78" -path = "./lib/bcpkix-jdk18on-1.78.jar" +version = "1.78.1" +path = "./lib/bcpkix-jdk18on-1.78.1.jar" [[platform.java17.dependency]] groupId = "org.bouncycastle" artifactId = "bcprov-jdk18on" -version = "1.78" -path = "./lib/bcprov-jdk18on-1.78.jar" +version = "1.78.1" +path = "./lib/bcprov-jdk18on-1.78.1.jar" [[platform.java17.dependency]] groupId = "org.bouncycastle" artifactId = "bcutil-jdk18on" -version = "1.78" -path = "./lib/bcutil-jdk18on-1.78.jar" +version = "1.78.1" +path = "./lib/bcutil-jdk18on-1.78.1.jar" [[platform.java17.dependency]] groupId = "org.bouncycastle" artifactId = "bcpg-jdk18on" -version = "1.78" -path = "./lib/bcpg-jdk18on-1.78.jar" +version = "1.78.1" +path = "./lib/bcpg-jdk18on-1.78.1.jar" From 1126113556dd560c863ae70c99fa0f9cc626ff58 Mon Sep 17 00:00:00 2001 From: TharmiganK Date: Tue, 8 Oct 2024 11:24:05 +0530 Subject: [PATCH 20/42] [Automated] Update the native jar versions --- ballerina/Ballerina.toml | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/ballerina/Ballerina.toml b/ballerina/Ballerina.toml index c0f9b2b..1ce6ae2 100644 --- a/ballerina/Ballerina.toml +++ b/ballerina/Ballerina.toml @@ -21,23 +21,23 @@ path = "../native/build/libs/crypto-native-2.8.0-SNAPSHOT.jar" [[platform.java17.dependency]] groupId = "org.bouncycastle" artifactId = "bcpkix-jdk18on" -version = "1.78.1" -path = "./lib/bcpkix-jdk18on-1.78.1.jar" +version = "1.78" +path = "./lib/bcpkix-jdk18on-1.78.jar" [[platform.java17.dependency]] groupId = "org.bouncycastle" artifactId = "bcprov-jdk18on" -version = "1.78.1" -path = "./lib/bcprov-jdk18on-1.78.1.jar" +version = "1.78" +path = "./lib/bcprov-jdk18on-1.78.jar" [[platform.java17.dependency]] groupId = "org.bouncycastle" artifactId = "bcutil-jdk18on" -version = "1.78.1" -path = "./lib/bcutil-jdk18on-1.78.1.jar" +version = "1.78" +path = "./lib/bcutil-jdk18on-1.78.jar" [[platform.java17.dependency]] groupId = "org.bouncycastle" artifactId = "bcpg-jdk18on" -version = "1.78.1" -path = "./lib/bcpg-jdk18on-1.78.1.jar" +version = "1.78" +path = "./lib/bcpg-jdk18on-1.78.jar" From ea708afda1558d38bf8f9e58217844c19c28abdc Mon Sep 17 00:00:00 2001 From: TharmiganK Date: Wed, 9 Oct 2024 09:39:10 +0530 Subject: [PATCH 21/42] Add stream pgp encrypt support --- ballerina/encrypt_decrypt.bal | 14 ++ ballerina/stream_iterator.bal | 33 ---- ballerina/stream_iterators.bal | 65 +++++++ .../stdlib/crypto/BallerinaInputStream.java | 92 +++++++--- .../io/ballerina/stdlib/crypto/Constants.java | 12 +- .../stdlib/crypto/PgpDecryptionGenerator.java | 18 +- .../stdlib/crypto/PgpEncryptionGenerator.java | 57 +++++- .../stdlib/crypto/nativeimpl/Decrypt.java | 29 ++-- .../stdlib/crypto/nativeimpl/Encrypt.java | 49 +++++- .../stdlib/crypto/nativeimpl/StreamUtils.java | 163 +++++++++++++++--- 10 files changed, 418 insertions(+), 114 deletions(-) delete mode 100644 ballerina/stream_iterator.bal create mode 100644 ballerina/stream_iterators.bal diff --git a/ballerina/encrypt_decrypt.bal b/ballerina/encrypt_decrypt.bal index 6e1e446..fa19096 100644 --- a/ballerina/encrypt_decrypt.bal +++ b/ballerina/encrypt_decrypt.bal @@ -276,6 +276,20 @@ public isolated function encryptPgpAsFile(string inputFilePath, string publicKey 'class: "io.ballerina.stdlib.crypto.nativeimpl.Encrypt" } external; +# Returns the PGP-encrypted stream of the content given in the input stream. +# ```ballerina +# stream inputStream = check io:fileReadBlocksAsStream("input.txt"); +# stream|Error encryptedStream = crypto:encryptStreamPgp(inputStream, "public_key.asc"); +# ``` +# +# + inputStream - The content to be encrypted as a stream +# + privateKeyPath - Path to the private key +# + return - Encrypted stream or else a `crypto:Error` if the key is invalid +public isolated function encryptStreamPgp(stream inputStream, string publicKeyPath, + *Options options) returns stream|Error = @java:Method { + 'class: "io.ballerina.stdlib.crypto.nativeimpl.Encrypt" +} external; + # Returns the PGP-decrypted value of the given PGP-encrypted data. # ```ballerina # byte[] message = "Hello Ballerina!".toBytes(); diff --git a/ballerina/stream_iterator.bal b/ballerina/stream_iterator.bal deleted file mode 100644 index cd36c28..0000000 --- a/ballerina/stream_iterator.bal +++ /dev/null @@ -1,33 +0,0 @@ -import ballerina/jballerina.java; - -class StreamIterator { - boolean isClosed = false; - - public isolated function next() returns record {|byte[] value;|}|Error? { - byte[]|Error? bytes = self.read(); - if bytes is byte[] { - return {value: bytes}; - } else { - return bytes; - } - } - - public isolated function close() returns Error? { - if !self.isClosed { - var closeResult = self.closeStream(); - if closeResult is () { - self.isClosed = true; - } - return closeResult; - } - return; - } - - isolated function read() returns byte[]|Error? = @java:Method { - 'class: "io.ballerina.stdlib.crypto.nativeimpl.StreamUtils" - } external; - - isolated function closeStream() returns Error? = @java:Method { - 'class: "io.ballerina.stdlib.crypto.nativeimpl.StreamUtils" - } external; -} diff --git a/ballerina/stream_iterators.bal b/ballerina/stream_iterators.bal new file mode 100644 index 0000000..df04711 --- /dev/null +++ b/ballerina/stream_iterators.bal @@ -0,0 +1,65 @@ +import ballerina/jballerina.java; + +class DecryptedStreamIterator { + boolean isClosed = false; + + public isolated function next() returns record {|byte[] value;|}|Error? { + byte[]|Error? bytes = self.readDecryptedStream(); + if bytes is byte[] { + return {value: bytes}; + } else { + return bytes; + } + } + + public isolated function close() returns Error? { + if !self.isClosed { + var closeResult = self.closeDecryptedStream(); + if closeResult is () { + self.isClosed = true; + } + return closeResult; + } + return; + } + + isolated function readDecryptedStream() returns byte[]|Error? = @java:Method { + 'class: "io.ballerina.stdlib.crypto.nativeimpl.StreamUtils" + } external; + + isolated function closeDecryptedStream() returns Error? = @java:Method { + 'class: "io.ballerina.stdlib.crypto.nativeimpl.StreamUtils" + } external; +} + +class EncryptedStreamIterator { + boolean isClosed = false; + + public isolated function next() returns record {|byte[] value;|}|Error? { + byte[]|Error? bytes = self.readEncryptedStream(); + if bytes is byte[] { + return {value: bytes}; + } else { + return bytes; + } + } + + public isolated function close() returns Error? { + if !self.isClosed { + var closeResult = self.closeEncryptedStream(); + if closeResult is () { + self.isClosed = true; + } + return closeResult; + } + return; + } + + isolated function readEncryptedStream() returns byte[]|Error? = @java:Method { + 'class: "io.ballerina.stdlib.crypto.nativeimpl.StreamUtils" + } external; + + isolated function closeEncryptedStream() returns Error? = @java:Method { + 'class: "io.ballerina.stdlib.crypto.nativeimpl.StreamUtils" + } external; +} diff --git a/native/src/main/java/io/ballerina/stdlib/crypto/BallerinaInputStream.java b/native/src/main/java/io/ballerina/stdlib/crypto/BallerinaInputStream.java index 368b72e..335f79b 100644 --- a/native/src/main/java/io/ballerina/stdlib/crypto/BallerinaInputStream.java +++ b/native/src/main/java/io/ballerina/stdlib/crypto/BallerinaInputStream.java @@ -37,9 +37,23 @@ * @since 2.8.0 */ public class BallerinaInputStream extends InputStream { + public static final String BAL_STREAM_CLOSE = "close"; + public static final String STREAM_VALUE = "value"; + public static final String BAL_STREAM_NEXT = "next"; + + public static final String ERROR_OCCURRED_WHILE_CLOSING_THE_STREAM = "Error occurred while closing the stream"; + public static final String ERROR_OCCURRED_WHILE_READING_THE_STREAM = "Error occurred while reading the next " + + "element from the stream"; + public static final String INTERRUPTED_ERROR_WHILE_READING_THE_STREAM = ERROR_OCCURRED_WHILE_READING_THE_STREAM + + ": interrupted exception"; + public static final String ERR_MSG_FORMAT = "%s: %s"; + public static final String UNEXPECTED_TYPE_ERROR = ERROR_OCCURRED_WHILE_READING_THE_STREAM + + ": unexpected value type"; + private final Environment environment; private final BStream ballerinaStream; private ByteBuffer buffer = null; + private boolean endOfStream = false; public BallerinaInputStream(Environment environment, BStream ballerinaStream) { this.ballerinaStream = ballerinaStream; @@ -48,40 +62,29 @@ public BallerinaInputStream(Environment environment, BStream ballerinaStream) { @Override public int read() throws IOException { + if (endOfStream) { + return -1; + } if (Objects.isNull(buffer) || !buffer.hasRemaining()) { - Object nextElement = getNext(); - if (nextElement instanceof BError) { - throw new IOException(((BError) nextElement).getMessage()); - } - if (Objects.isNull(nextElement)) { + boolean result = pollNext(); + if (!result) { + endOfStream = true; return -1; } - if (nextElement instanceof BMap nextValue) { - Object nextBytes = nextValue.get(StringUtils.fromString("value")); - if (nextBytes instanceof BArray) { - buffer = ByteBuffer.wrap(((BArray) nextBytes).getBytes()); - } else { - throw new IOException("Error occurred while reading the next element from the stream: " + - "unexpected value type"); - } - } else { - throw new IOException("Error occurred while reading the next element from the stream: " + - "unexpected value type"); - } } return buffer.get() & 0xFF; } @Override - public void close() { - Object result = callBallerinaFunction("close", "Error occurred while closing the stream"); - if (result instanceof BError) { - throw new RuntimeException(((BError) result).getMessage()); + public void close() throws IOException { + Object result = callBallerinaFunction(BAL_STREAM_CLOSE, ERROR_OCCURRED_WHILE_CLOSING_THE_STREAM); + if (result instanceof BError bError) { + throw new IOException((bError).getMessage()); } } public Object getNext() { - return callBallerinaFunction("next", "Error occurred while reading the next element from the stream"); + return callBallerinaFunction(BAL_STREAM_NEXT, ERROR_OCCURRED_WHILE_READING_THE_STREAM); } private Object callBallerinaFunction(String functionName, String message) { @@ -94,8 +97,8 @@ private Object callBallerinaFunction(String functionName, String message) { try { countDownLatch.await(); } catch (InterruptedException exception) { - return CryptoUtils.createError("Error occurred while reading the next element from the stream: " + - "interrupted exception"); + Thread.currentThread().interrupt(); + return CryptoUtils.createError(INTERRUPTED_ERROR_WHILE_READING_THE_STREAM); } return nextResult[0]; } @@ -111,10 +114,49 @@ public void notifySuccess(Object result) { @Override public void notifyFailure(BError bError) { - BError error = CryptoUtils.createError(String.format("%s: %s", message, bError.getMessage())); + BError error = CryptoUtils.createError(String.format(ERR_MSG_FORMAT, message, bError.getMessage())); nextResult[0] = error; countDownLatch.countDown(); } } + + @Override + public int read(byte[] b, int off, int len) throws IOException { + if (endOfStream) { + return -1; + } + if (Objects.isNull(buffer) || !buffer.hasRemaining()) { + boolean result = pollNext(); + if (!result) { + endOfStream = true; + return -1; + } + } + int remaining = buffer.remaining(); + int readLength = Math.min(remaining, len); + buffer.get(b, off, readLength); + return readLength; + } + + private boolean pollNext() throws IOException { + Object nextElement = getNext(); + if (nextElement instanceof BError bError) { + throw new IllegalStateException((bError).getMessage()); + } + if (Objects.isNull(nextElement)) { + return false; + } + if (nextElement instanceof BMap nextValue) { + Object nextBytes = nextValue.get(StringUtils.fromString(STREAM_VALUE)); + if (nextBytes instanceof BArray nextBytesArray) { + buffer = ByteBuffer.wrap((nextBytesArray).getBytes()); + } else { + throw new IOException(UNEXPECTED_TYPE_ERROR); + } + } else { + throw new IOException(UNEXPECTED_TYPE_ERROR); + } + return true; + } } diff --git a/native/src/main/java/io/ballerina/stdlib/crypto/Constants.java b/native/src/main/java/io/ballerina/stdlib/crypto/Constants.java index b084fca..749ed20 100644 --- a/native/src/main/java/io/ballerina/stdlib/crypto/Constants.java +++ b/native/src/main/java/io/ballerina/stdlib/crypto/Constants.java @@ -125,7 +125,13 @@ private Constants() {} public static final String AES = "AES"; public static final String RSA = "RSA"; - public static final String DECRYPTED_STREAM = "INPUT_STREAM"; - public static final String COMPRESSED_PGP_STREAM = "COMPRESSED_PGP_STREAM"; - public static final String COMPRESSED_STREAM = "COMPRESSED_STREAM"; + public static final String COMPRESSED_DATA_STREAM = "COMPRESSED_DATA_STREAM"; + public static final String DATA_STREAM = "DATA_STREAM"; + public static final String TARGET_STREAM = "TARGET_STREAM"; + public static final String ENCRYPTED_OUTPUT_STREAM = "ENCRYPTED_OUTPUT_STREAM"; + public static final String INPUT_STREAM_TO_ENCRYPT = "INPUT_STREAM_TO_ENCRYPT"; + public static final String PIPED_INPUT_STREAM = "PIPED_INPUT_STREAM"; + public static final String PIPED_OUTPUT_STREAM = "PIPED_OUTPUT_STREAM"; + public static final String ENCRYPTION_STARTED = "ENCRYPTION_STARTED"; + public static final String COMPRESSED_DATA_GENERATOR = "COMPRESSED_DATA_GENERATOR"; } diff --git a/native/src/main/java/io/ballerina/stdlib/crypto/PgpDecryptionGenerator.java b/native/src/main/java/io/ballerina/stdlib/crypto/PgpDecryptionGenerator.java index a4fe3c9..5bd939a 100644 --- a/native/src/main/java/io/ballerina/stdlib/crypto/PgpDecryptionGenerator.java +++ b/native/src/main/java/io/ballerina/stdlib/crypto/PgpDecryptionGenerator.java @@ -51,9 +51,9 @@ import java.util.Objects; import java.util.Optional; -import static io.ballerina.stdlib.crypto.Constants.COMPRESSED_PGP_STREAM; -import static io.ballerina.stdlib.crypto.Constants.COMPRESSED_STREAM; -import static io.ballerina.stdlib.crypto.Constants.DECRYPTED_STREAM; +import static io.ballerina.stdlib.crypto.Constants.COMPRESSED_DATA_STREAM; +import static io.ballerina.stdlib.crypto.Constants.DATA_STREAM; +import static io.ballerina.stdlib.crypto.Constants.TARGET_STREAM; /** * Provides functionality for PGP decryption operations. @@ -121,7 +121,7 @@ private void decryptStream(InputStream encryptedIn, OutputStream clearOut) decrypt(clearOut, pgpPrivateKey.get(), publicKeyEncryptedData); } - private void decryptStream(InputStream encryptedIn, BObject iteratorObj) throws PGPException, IOException { + public void decryptStream(InputStream encryptedIn, BObject iteratorObj) throws PGPException, IOException { // Remove armour and return the underlying binary encrypted stream encryptedIn = PGPUtil.getDecoderStream(encryptedIn); JcaPGPObjectFactory pgpObjectFactory = new JcaPGPObjectFactory(encryptedIn); @@ -159,10 +159,6 @@ public Object decrypt(byte[] encryptedBytes) throws PGPException, IOException { } } - public void decrypt(InputStream encryptedIn, BObject iteratorObj) throws PGPException, IOException { - decryptStream(encryptedIn, iteratorObj); - } - public void decrypt(InputStream encryptedIn, String outputPath) throws PGPException, IOException { try (OutputStream outputStream = Files.newOutputStream(Path.of(outputPath))) { decryptStream(encryptedIn, outputStream); @@ -226,9 +222,9 @@ private static void decrypt(PGPPrivateKey pgpPrivateKey, PGPPublicKeyEncryptedDa throw new PGPException("Message failed integrity check"); } } - iteratorObj.addNativeData(DECRYPTED_STREAM, pgpLiteralData.getDataStream()); - iteratorObj.addNativeData(COMPRESSED_PGP_STREAM, compressedDataStream); - iteratorObj.addNativeData(COMPRESSED_STREAM, decryptedCompressedIn); + iteratorObj.addNativeData(TARGET_STREAM, pgpLiteralData.getDataStream()); + iteratorObj.addNativeData(COMPRESSED_DATA_STREAM, compressedDataStream); + iteratorObj.addNativeData(DATA_STREAM, decryptedCompressedIn); } else if (message instanceof PGPOnePassSignatureList) { throw new PGPException("Encrypted message contains a signed message not literal data"); } else { diff --git a/native/src/main/java/io/ballerina/stdlib/crypto/PgpEncryptionGenerator.java b/native/src/main/java/io/ballerina/stdlib/crypto/PgpEncryptionGenerator.java index 6774966..99bfa05 100644 --- a/native/src/main/java/io/ballerina/stdlib/crypto/PgpEncryptionGenerator.java +++ b/native/src/main/java/io/ballerina/stdlib/crypto/PgpEncryptionGenerator.java @@ -18,6 +18,7 @@ package io.ballerina.stdlib.crypto; import io.ballerina.runtime.api.creators.ValueCreator; +import io.ballerina.runtime.api.values.BObject; import org.bouncycastle.bcpg.ArmoredOutputStream; import org.bouncycastle.jce.provider.BouncyCastleProvider; import org.bouncycastle.openpgp.PGPCompressedDataGenerator; @@ -38,6 +39,8 @@ import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; +import java.io.PipedInputStream; +import java.io.PipedOutputStream; import java.nio.file.Files; import java.nio.file.Path; import java.security.SecureRandom; @@ -50,6 +53,13 @@ import java.util.Objects; import java.util.Optional; +import static io.ballerina.stdlib.crypto.Constants.COMPRESSED_DATA_GENERATOR; +import static io.ballerina.stdlib.crypto.Constants.DATA_STREAM; +import static io.ballerina.stdlib.crypto.Constants.ENCRYPTED_OUTPUT_STREAM; +import static io.ballerina.stdlib.crypto.Constants.PIPED_INPUT_STREAM; +import static io.ballerina.stdlib.crypto.Constants.PIPED_OUTPUT_STREAM; +import static io.ballerina.stdlib.crypto.Constants.TARGET_STREAM; + /** * Provides functionality for PGP encryption operations. * @@ -67,7 +77,7 @@ public class PgpEncryptionGenerator { private final int symmetricKeyAlgorithm; private final boolean armor; private final boolean withIntegrityCheck; - private static final int BUFFER_SIZE = 8192; + public static final int BUFFER_SIZE = 8192; // The constructor of the PGP encryption generator. public PgpEncryptionGenerator(int compressionAlgorithm, int symmetricKeyAlgorithm, boolean armor, @@ -80,8 +90,7 @@ public PgpEncryptionGenerator(int compressionAlgorithm, int symmetricKeyAlgorith private void encryptStream(OutputStream encryptOut, InputStream clearIn, InputStream publicKeyIn) throws IOException, PGPException { - PGPCompressedDataGenerator compressedDataGenerator = - new PGPCompressedDataGenerator(compressionAlgorithm); + PGPCompressedDataGenerator compressedDataGenerator = new PGPCompressedDataGenerator(compressionAlgorithm); PGPEncryptedDataGenerator pgpEncryptedDataGenerator = new PGPEncryptedDataGenerator( // Configure the encrypted data generator new JcePGPDataEncryptorBuilder(symmetricKeyAlgorithm) @@ -103,6 +112,36 @@ private void encryptStream(OutputStream encryptOut, InputStream clearIn, InputSt encryptOut.close(); } + public void encryptStream(InputStream publicKeyIn, BObject iteratorObj) + throws IOException, PGPException { + OutputStream encryptOut = new PipedOutputStream(); + iteratorObj.addNativeData(PIPED_OUTPUT_STREAM, encryptOut); + PipedInputStream pipedInputStream = new PipedInputStream((PipedOutputStream) encryptOut, + PgpEncryptionGenerator.BUFFER_SIZE); + iteratorObj.addNativeData(PIPED_INPUT_STREAM, pipedInputStream); + PGPCompressedDataGenerator compressedDataGenerator = new PGPCompressedDataGenerator(compressionAlgorithm); + PGPEncryptedDataGenerator pgpEncryptedDataGenerator = new PGPEncryptedDataGenerator( + // Configure the encrypted data generator + new JcePGPDataEncryptorBuilder(symmetricKeyAlgorithm) + .setWithIntegrityPacket(withIntegrityCheck) + .setSecureRandom(new SecureRandom()) + .setProvider(BouncyCastleProvider.PROVIDER_NAME) + ); + // Add public key + pgpEncryptedDataGenerator.addMethod(new JcePublicKeyKeyEncryptionMethodGenerator( + getPublicKey(publicKeyIn))); + if (armor) { + encryptOut = new ArmoredOutputStream(encryptOut); + } + + iteratorObj.addNativeData(ENCRYPTED_OUTPUT_STREAM, encryptOut); + OutputStream cipherOutStream = pgpEncryptedDataGenerator.open(encryptOut, new byte[BUFFER_SIZE]); + OutputStream compressedOutStream = compressedDataGenerator.open(cipherOutStream); + iteratorObj.addNativeData(DATA_STREAM, cipherOutStream); + iteratorObj.addNativeData(COMPRESSED_DATA_GENERATOR, compressedDataGenerator); + copyAsLiteralData(compressedOutStream, iteratorObj); + } + // Encrypts the given byte array of plain text data using PGP encryption. public Object encrypt(byte[] clearData, InputStream publicKeyIn) throws PGPException, IOException { try (ByteArrayInputStream inputStream = new ByteArrayInputStream(clearData); @@ -136,9 +175,9 @@ private static PGPPublicKey getPublicKey(InputStream keyInputStream) throws IOEx private static void copyAsLiteralData(OutputStream outputStream, InputStream in) throws IOException { PGPLiteralDataGenerator lData = new PGPLiteralDataGenerator(); - byte[] buff = new byte[PgpEncryptionGenerator.BUFFER_SIZE]; + byte[] buff = new byte[BUFFER_SIZE]; try (OutputStream pOut = lData.open(outputStream, PGPLiteralData.BINARY, PGPLiteralData.CONSOLE, - Date.from(LocalDateTime.now().toInstant(ZoneOffset.UTC)), new byte[PgpEncryptionGenerator.BUFFER_SIZE]); + Date.from(LocalDateTime.now().toInstant(ZoneOffset.UTC)), new byte[BUFFER_SIZE]); InputStream inputStream = in) { int len; @@ -150,6 +189,14 @@ private static void copyAsLiteralData(OutputStream outputStream, InputStream in) } } + private static void copyAsLiteralData(OutputStream outputStream, BObject iteratorObj) + throws IOException { + PGPLiteralDataGenerator lData = new PGPLiteralDataGenerator(); + OutputStream pOut = lData.open(outputStream, PGPLiteralData.BINARY, PGPLiteralData.CONSOLE, + Date.from(LocalDateTime.now().toInstant(ZoneOffset.UTC)), new byte[BUFFER_SIZE]); + iteratorObj.addNativeData(TARGET_STREAM, pOut); + } + private static Optional extractPgpKeyFromRing(PGPPublicKeyRing pgpPublicKeyRing) { for (PGPPublicKey publicKey : pgpPublicKeyRing) { if (publicKey.isEncryptionKey()) { diff --git a/native/src/main/java/io/ballerina/stdlib/crypto/nativeimpl/Decrypt.java b/native/src/main/java/io/ballerina/stdlib/crypto/nativeimpl/Decrypt.java index 03baebb..b433372 100644 --- a/native/src/main/java/io/ballerina/stdlib/crypto/nativeimpl/Decrypt.java +++ b/native/src/main/java/io/ballerina/stdlib/crypto/nativeimpl/Decrypt.java @@ -50,6 +50,10 @@ */ public class Decrypt { + public static final String ERROR_OCCURRED_WHILE_PGP_DECRYPT = "Error occurred while PGP decrypt: "; + public static final String ERROR_OCCURRED_WHILE_READING_PRIVATE_KEY = "Error occurred while reading private key: "; + public static final String UNINITIALIZED_PRIVATE_PUBLIC_KEY = "Uninitialized private/public key."; + private Decrypt() {} public static Object decryptAesCbc(BArray inputValue, BArray keyValue, BArray ivValue, Object padding) { @@ -85,7 +89,7 @@ public static Object decryptRsaEcb(BArray inputValue, Object keys, Object paddin } else if (keyMap.getNativeData(Constants.NATIVE_DATA_PUBLIC_KEY) != null) { key = (PublicKey) keyMap.getNativeData(Constants.NATIVE_DATA_PUBLIC_KEY); } else { - return CryptoUtils.createError("Uninitialized private/public key."); + return CryptoUtils.createError(UNINITIALIZED_PRIVATE_PUBLIC_KEY); } return CryptoUtils.rsaEncryptDecrypt(CryptoUtils.CipherMode.DECRYPT, Constants.ECB, padding.toString(), key, input, null, -1); @@ -98,14 +102,14 @@ public static Object decryptPgp(BArray cipherTextValue, BString privateKeyPath, try { privateKey = Files.readAllBytes(Path.of(privateKeyPath.toString())); } catch (IOException e) { - return CryptoUtils.createError("Error occurred while reading private key: " + e.getMessage()); + return CryptoUtils.createError(ERROR_OCCURRED_WHILE_READING_PRIVATE_KEY + e.getMessage()); } try (InputStream keyStream = new ByteArrayInputStream(privateKey)) { PgpDecryptionGenerator pgpDecryptionGenerator = new PgpDecryptionGenerator(keyStream, passphraseInBytes); return pgpDecryptionGenerator.decrypt(cipherText); } catch (IOException | PGPException e) { - return CryptoUtils.createError("Error occurred while PGP decrypt: " + e.getMessage()); + return CryptoUtils.createError(ERROR_OCCURRED_WHILE_PGP_DECRYPT + e.getMessage()); } } @@ -116,7 +120,7 @@ public static Object decryptPgpAsFile(BString inputFilePath, BString privateKeyP try { privateKey = Files.readAllBytes(Path.of(privateKeyPath.toString())); } catch (IOException e) { - return CryptoUtils.createError("Error occurred while reading private key: " + e.getMessage()); + return CryptoUtils.createError(ERROR_OCCURRED_WHILE_READING_PRIVATE_KEY + e.getMessage()); } try (InputStream keyStream = new ByteArrayInputStream(privateKey); @@ -126,30 +130,29 @@ public static Object decryptPgpAsFile(BString inputFilePath, BString privateKeyP pgpDecryptionGenerator.decrypt(cipherTextStream, outputFilePath.getValue()); return null; } catch (IOException | PGPException e) { - return CryptoUtils.createError("Error occurred while PGP decrypt: " + e.getMessage()); + return CryptoUtils.createError(ERROR_OCCURRED_WHILE_PGP_DECRYPT + e.getMessage()); } } - public static Object decryptStreamPgp(Environment environment, BStream inputStream, BString privateKeyPath, + public static Object decryptStreamPgp(Environment environment, BStream inputBalStream, BString privateKeyPath, BArray passphrase) { byte[] passphraseInBytes = passphrase.getBytes(); byte[] privateKey; try { privateKey = Files.readAllBytes(Path.of(privateKeyPath.toString())); } catch (IOException e) { - return CryptoUtils.createError("Error occurred while reading private key: " + e.getMessage()); + return CryptoUtils.createError(ERROR_OCCURRED_WHILE_READING_PRIVATE_KEY + e.getMessage()); } try (InputStream keyStream = new ByteArrayInputStream(privateKey)) { - InputStream cipherTextStream = new BallerinaInputStream(environment, inputStream); + InputStream cipherTextStream = new BallerinaInputStream(environment, inputBalStream); PgpDecryptionGenerator pgpDecryptionGenerator = new PgpDecryptionGenerator(keyStream, passphraseInBytes); - BObject iteratorObj = ValueCreator.createObjectValue(ModuleUtils.getModule(), "StreamIterator"); - pgpDecryptionGenerator.decrypt(cipherTextStream, iteratorObj); + BObject iteratorObj = ValueCreator.createObjectValue(ModuleUtils.getModule(), "DecryptedStreamIterator"); + pgpDecryptionGenerator.decryptStream(cipherTextStream, iteratorObj); Type constrainedType = TypeCreator.createArrayType(PredefinedTypes.TYPE_BYTE); - return ValueCreator.createStreamValue(TypeCreator.createStreamType(constrainedType), - iteratorObj); + return ValueCreator.createStreamValue(TypeCreator.createStreamType(constrainedType), iteratorObj); } catch (IOException | PGPException e) { - return CryptoUtils.createError("Error occurred while PGP decrypt: " + e.getMessage()); + return CryptoUtils.createError(ERROR_OCCURRED_WHILE_PGP_DECRYPT + e.getMessage()); } } } diff --git a/native/src/main/java/io/ballerina/stdlib/crypto/nativeimpl/Encrypt.java b/native/src/main/java/io/ballerina/stdlib/crypto/nativeimpl/Encrypt.java index 2f4dc48..0ded0ea 100644 --- a/native/src/main/java/io/ballerina/stdlib/crypto/nativeimpl/Encrypt.java +++ b/native/src/main/java/io/ballerina/stdlib/crypto/nativeimpl/Encrypt.java @@ -18,10 +18,18 @@ package io.ballerina.stdlib.crypto.nativeimpl; +import io.ballerina.runtime.api.Environment; +import io.ballerina.runtime.api.PredefinedTypes; +import io.ballerina.runtime.api.creators.TypeCreator; +import io.ballerina.runtime.api.creators.ValueCreator; +import io.ballerina.runtime.api.types.Type; import io.ballerina.runtime.api.utils.StringUtils; import io.ballerina.runtime.api.values.BArray; import io.ballerina.runtime.api.values.BMap; +import io.ballerina.runtime.api.values.BObject; +import io.ballerina.runtime.api.values.BStream; import io.ballerina.runtime.api.values.BString; +import io.ballerina.stdlib.crypto.BallerinaInputStream; import io.ballerina.stdlib.crypto.Constants; import io.ballerina.stdlib.crypto.CryptoUtils; import io.ballerina.stdlib.crypto.PgpEncryptionGenerator; @@ -36,6 +44,9 @@ import java.security.PrivateKey; import java.security.PublicKey; +import static io.ballerina.stdlib.crypto.Constants.ENCRYPTION_STARTED; +import static io.ballerina.stdlib.crypto.Constants.INPUT_STREAM_TO_ENCRYPT; + /** * Extern functions ballerina encrypt algorithms. * @@ -47,6 +58,8 @@ public class Encrypt { private static final BString SYMMETRIC_KEY_ALGORITHM = StringUtils.fromString("symmetricKeyAlgorithm"); private static final BString ARMOR = StringUtils.fromString("armor"); private static final BString WITH_INTEGRITY_CHECK = StringUtils.fromString("withIntegrityCheck"); + public static final String ERROR_OCCURRED_WHILE_READING_PUBLIC_KEY = "Error occurred while reading public key: "; + public static final String ERROR_OCCURRED_WHILE_PGP_ENCRYPT = "Error occurred while PGP encrypt: "; private Encrypt() {} @@ -101,7 +114,7 @@ public static Object encryptPgp(BArray plainTextValue, BString publicKeyPath, BM try { publicKey = Files.readAllBytes(Path.of(publicKeyPath.toString())); } catch (IOException e) { - return CryptoUtils.createError("Error occurred while reading public key: " + e.getMessage()); + return CryptoUtils.createError(ERROR_OCCURRED_WHILE_READING_PUBLIC_KEY + e.getMessage()); } try (InputStream publicKeyStream = new ByteArrayInputStream(publicKey)) { @@ -113,7 +126,7 @@ public static Object encryptPgp(BArray plainTextValue, BString publicKeyPath, BM ); return pgpEncryptionGenerator.encrypt(plainText, publicKeyStream); } catch (IOException | PGPException e) { - return CryptoUtils.createError("Error occurred while PGP encrypt: " + e.getMessage()); + return CryptoUtils.createError(ERROR_OCCURRED_WHILE_PGP_ENCRYPT + e.getMessage()); } } @@ -123,7 +136,7 @@ public static Object encryptPgpAsFile(BString inputFilePath, BString publicKeyPa try { publicKey = Files.readAllBytes(Path.of(publicKeyPath.toString())); } catch (IOException e) { - return CryptoUtils.createError("Error occurred while reading public key: " + e.getMessage()); + return CryptoUtils.createError(ERROR_OCCURRED_WHILE_READING_PUBLIC_KEY + e.getMessage()); } try (InputStream publicKeyStream = new ByteArrayInputStream(publicKey); @@ -138,7 +151,35 @@ public static Object encryptPgpAsFile(BString inputFilePath, BString publicKeyPa pgpEncryptionGenerator.encrypt(inputStream, publicKeyStream, outputFilePath.getValue()); return null; } catch (IOException | PGPException e) { - return CryptoUtils.createError("Error occurred while PGP encrypt: " + e.getMessage()); + return CryptoUtils.createError(ERROR_OCCURRED_WHILE_PGP_ENCRYPT + e.getMessage()); + } + } + + public static Object encryptStreamPgp(Environment environment, BStream inputBalStream, BString publicKeyPath, + BMap options) { + byte[] publicKey; + try { + publicKey = Files.readAllBytes(Path.of(publicKeyPath.toString())); + } catch (IOException e) { + return CryptoUtils.createError(ERROR_OCCURRED_WHILE_READING_PUBLIC_KEY + e.getMessage()); + } + + try (InputStream publicKeyStream = new ByteArrayInputStream(publicKey)) { + InputStream inputStream = new BallerinaInputStream(environment, inputBalStream); + PgpEncryptionGenerator pgpEncryptionGenerator = new PgpEncryptionGenerator( + Integer.parseInt(options.get(COMPRESSION_ALGORITHM).toString()), + Integer.parseInt(options.get(SYMMETRIC_KEY_ALGORITHM).toString()), + Boolean.parseBoolean(options.get(ARMOR).toString()), + Boolean.parseBoolean(options.get(WITH_INTEGRITY_CHECK).toString()) + ); + BObject iteratorObj = ValueCreator.createObjectValue(ModuleUtils.getModule(), "EncryptedStreamIterator"); + iteratorObj.addNativeData(ENCRYPTION_STARTED, false); + iteratorObj.addNativeData(INPUT_STREAM_TO_ENCRYPT, inputStream); + pgpEncryptionGenerator.encryptStream(publicKeyStream, iteratorObj); + Type constrainedType = TypeCreator.createArrayType(PredefinedTypes.TYPE_BYTE); + return ValueCreator.createStreamValue(TypeCreator.createStreamType(constrainedType), iteratorObj); + } catch (IOException | PGPException e) { + return CryptoUtils.createError(ERROR_OCCURRED_WHILE_PGP_ENCRYPT + e.getMessage()); } } } diff --git a/native/src/main/java/io/ballerina/stdlib/crypto/nativeimpl/StreamUtils.java b/native/src/main/java/io/ballerina/stdlib/crypto/nativeimpl/StreamUtils.java index 0949048..4282f98 100644 --- a/native/src/main/java/io/ballerina/stdlib/crypto/nativeimpl/StreamUtils.java +++ b/native/src/main/java/io/ballerina/stdlib/crypto/nativeimpl/StreamUtils.java @@ -18,16 +18,29 @@ package io.ballerina.stdlib.crypto.nativeimpl; import io.ballerina.runtime.api.creators.ValueCreator; +import io.ballerina.runtime.api.values.BArray; +import io.ballerina.runtime.api.values.BError; import io.ballerina.runtime.api.values.BObject; import io.ballerina.stdlib.crypto.CryptoUtils; +import org.bouncycastle.openpgp.PGPCompressedDataGenerator; +import java.io.Closeable; import java.io.IOException; import java.io.InputStream; +import java.io.OutputStream; +import java.io.PipedInputStream; import java.util.Objects; -import static io.ballerina.stdlib.crypto.Constants.COMPRESSED_PGP_STREAM; -import static io.ballerina.stdlib.crypto.Constants.COMPRESSED_STREAM; -import static io.ballerina.stdlib.crypto.Constants.DECRYPTED_STREAM; +import static io.ballerina.stdlib.crypto.Constants.COMPRESSED_DATA_GENERATOR; +import static io.ballerina.stdlib.crypto.Constants.ENCRYPTED_OUTPUT_STREAM; +import static io.ballerina.stdlib.crypto.Constants.ENCRYPTION_STARTED; +import static io.ballerina.stdlib.crypto.Constants.INPUT_STREAM_TO_ENCRYPT; +import static io.ballerina.stdlib.crypto.Constants.PIPED_INPUT_STREAM; +import static io.ballerina.stdlib.crypto.Constants.COMPRESSED_DATA_STREAM; +import static io.ballerina.stdlib.crypto.Constants.DATA_STREAM; +import static io.ballerina.stdlib.crypto.Constants.PIPED_OUTPUT_STREAM; +import static io.ballerina.stdlib.crypto.Constants.TARGET_STREAM; +import static io.ballerina.stdlib.crypto.PgpEncryptionGenerator.BUFFER_SIZE; /** * Provides functionality for stream operations. @@ -36,16 +49,24 @@ */ public final class StreamUtils { + public static final String STREAM_NOT_AVAILABLE = "Stream is not available"; + public static final String ERROR_OCCURRED_WHILE_CLOSING_THE_STREAM = "Error occurred while closing the stream: %s"; + public static final String ERROR_OCCURRED_WHILE_CLOSING_THE_GENERATOR = "Error occurred while closing the " + + "generator: %s"; + public static final String ERROR_OCCURRED_WHILE_READING_THE_STREAM = "Error occurred while reading from the " + + "stream: %s"; + public static final String NATIVE_DATA_NOT_AVAILABLE_ERROR = "%s is not available"; + private StreamUtils() { } - public static Object read(BObject iterator) { - Object stream = iterator.getNativeData(DECRYPTED_STREAM); + public static Object readDecryptedStream(BObject iterator) { + Object stream = iterator.getNativeData(TARGET_STREAM); if (Objects.isNull(stream) || !(stream instanceof InputStream inputStream)) { - return CryptoUtils.createError("Stream is not available"); + return CryptoUtils.createError(String.format(NATIVE_DATA_NOT_AVAILABLE_ERROR, TARGET_STREAM)); } try { - byte[] buffer = new byte[4096]; + byte[] buffer = new byte[BUFFER_SIZE]; int in = inputStream.read(buffer); if (in == -1) { return null; @@ -61,24 +82,126 @@ public static Object read(BObject iterator) { } } - public static Object closeStream(BObject iterator) { - Object result = closeNativeStream(iterator, DECRYPTED_STREAM); - // Ignore the errors occurred while closing the compressed streams. - closeNativeStream(iterator, COMPRESSED_PGP_STREAM); - closeNativeStream(iterator, COMPRESSED_STREAM); - return result; + public static Object readEncryptedStream(BObject iterator) { + NativeData nativeData = getNativeData(iterator); + + try { + if (Boolean.FALSE.equals(nativeData.encryptionStarted())) { + iterator.addNativeData(ENCRYPTION_STARTED, true); + Thread writer = new Thread(() -> { + try { + writeToOutStream(iterator, nativeData.inputStream(), nativeData.outputStream()); + } catch (IOException e) { + throw new RuntimeException(e); + } + }); + writer.start(); + } + return readFromPipedStream(nativeData.pipedInStream()); + } catch (IOException e) { + return CryptoUtils.createError(String.format(ERROR_OCCURRED_WHILE_READING_THE_STREAM, e.getMessage())); + } catch (BError e) { + return e; + } } - public static Object closeNativeStream(BObject iterator, String streamName) { - Object stream = iterator.getNativeData(streamName); - if (Objects.isNull(stream) || !(stream instanceof InputStream inputStream)) { - return CryptoUtils.createError("Stream is not available"); + private static NativeData getNativeData(BObject iterator) { + Object inputStreamToEncrypt = iterator.getNativeData(INPUT_STREAM_TO_ENCRYPT); + if (Objects.isNull(inputStreamToEncrypt) || !(inputStreamToEncrypt instanceof InputStream inputStream)) { + throw CryptoUtils.createError(String.format(NATIVE_DATA_NOT_AVAILABLE_ERROR, INPUT_STREAM_TO_ENCRYPT)); + } + + Object targetStream = iterator.getNativeData(TARGET_STREAM); + if (Objects.isNull(targetStream) || !(targetStream instanceof OutputStream outputStream)) { + throw CryptoUtils.createError(String.format(NATIVE_DATA_NOT_AVAILABLE_ERROR, TARGET_STREAM)); + } + + Object pipelinedInputStream = iterator.getNativeData(PIPED_INPUT_STREAM); + if (Objects.isNull(pipelinedInputStream) || + !(pipelinedInputStream instanceof PipedInputStream pipedInStream)) { + throw CryptoUtils.createError(String.format(NATIVE_DATA_NOT_AVAILABLE_ERROR, PIPED_INPUT_STREAM)); + } + + Object encryptionStartedObj = iterator.getNativeData(ENCRYPTION_STARTED); + if (Objects.isNull(encryptionStartedObj) || !(encryptionStartedObj instanceof Boolean encryptionStarted)) { + throw CryptoUtils.createError(String.format(NATIVE_DATA_NOT_AVAILABLE_ERROR, ENCRYPTION_STARTED)); + } + return new NativeData(inputStream, outputStream, pipedInStream, encryptionStarted); + } + + private record NativeData(InputStream inputStream, OutputStream outputStream, PipedInputStream pipedInStream, + Boolean encryptionStarted) { + } + + private static BArray readFromPipedStream(PipedInputStream pipedInStream) throws IOException { + byte[] pipelinedBuffer = new byte[BUFFER_SIZE]; + int pipelinedIn = pipedInStream.read(pipelinedBuffer); + if (pipelinedIn == -1) { + return null; + } + if (pipelinedIn < pipelinedBuffer.length) { + byte[] temp = new byte[pipelinedIn]; + System.arraycopy(pipelinedBuffer, 0, temp, 0, pipelinedIn); + return ValueCreator.createArrayValue(temp); + } + return ValueCreator.createArrayValue(pipelinedBuffer); + } + + private static void writeToOutStream(BObject iterator, InputStream inputStream, OutputStream outputStream) + throws IOException { + byte[] inputBuffer = new byte[BUFFER_SIZE]; + int len; + while ((len = inputStream.read(inputBuffer)) > 0) { + outputStream.write(inputBuffer, 0, len); + } + closeEncryptedSourceStreams(iterator); + } + + public static void closeDecryptedStream(BObject iterator) throws BError { + closeNativeStream(iterator, TARGET_STREAM); + closeNativeStream(iterator, COMPRESSED_DATA_STREAM); + closeNativeStream(iterator, DATA_STREAM); + } + + public static void closeEncryptedStream(BObject iterator) throws BError { + closeNativeStream(iterator, TARGET_STREAM); + closeDataGenerator(iterator); + closeNativeStream(iterator, DATA_STREAM); + closeNativeStream(iterator, ENCRYPTED_OUTPUT_STREAM); + closeNativeStream(iterator, PIPED_OUTPUT_STREAM); + closeNativeStream(iterator, PIPED_INPUT_STREAM); + } + + public static void closeEncryptedSourceStreams(BObject iterator) throws BError { + closeNativeStream(iterator, INPUT_STREAM_TO_ENCRYPT); + closeNativeStream(iterator, TARGET_STREAM); + closeDataGenerator(iterator); + closeNativeStream(iterator, DATA_STREAM); + closeNativeStream(iterator, ENCRYPTED_OUTPUT_STREAM); + closeNativeStream(iterator, PIPED_OUTPUT_STREAM); + } + + public static void closeNativeStream(BObject iterator, String streamName) throws BError { + Object streamObj = iterator.getNativeData(streamName); + if (Objects.isNull(streamObj) || !(streamObj instanceof Closeable stream)) { + throw CryptoUtils.createError(STREAM_NOT_AVAILABLE); + } + try { + stream.close(); + } catch (IOException e) { + throw CryptoUtils.createError(String.format(ERROR_OCCURRED_WHILE_CLOSING_THE_STREAM, e.getMessage())); + } + } + + public static void closeDataGenerator(BObject iterator) throws BError { + Object generatorObj = iterator.getNativeData(COMPRESSED_DATA_GENERATOR); + if (Objects.isNull(generatorObj) || !(generatorObj instanceof PGPCompressedDataGenerator generator)) { + throw CryptoUtils.createError(STREAM_NOT_AVAILABLE); } try { - inputStream.close(); + generator.close(); } catch (IOException e) { - return CryptoUtils.createError("Error occurred while closing the stream: " + e.getMessage()); + throw CryptoUtils.createError(String.format(ERROR_OCCURRED_WHILE_CLOSING_THE_GENERATOR, e.getMessage())); } - return null; } } From 7094e49ebf4679e1d68b05612bb329be681a24fb Mon Sep 17 00:00:00 2001 From: TharmiganK Date: Wed, 9 Oct 2024 11:41:15 +0530 Subject: [PATCH 22/42] Replace PipedStreams with a custom implementation --- .../io/ballerina/stdlib/crypto/Constants.java | 2 +- .../stdlib/crypto/PgpEncryptionGenerator.java | 8 +-- .../stdlib/crypto/SequentialBufferedPipe.java | 72 +++++++++++++++++++ .../stdlib/crypto/nativeimpl/Encrypt.java | 4 +- .../stdlib/crypto/nativeimpl/StreamUtils.java | 43 +++++------ 5 files changed, 96 insertions(+), 33 deletions(-) create mode 100644 native/src/main/java/io/ballerina/stdlib/crypto/SequentialBufferedPipe.java diff --git a/native/src/main/java/io/ballerina/stdlib/crypto/Constants.java b/native/src/main/java/io/ballerina/stdlib/crypto/Constants.java index 749ed20..279bff8 100644 --- a/native/src/main/java/io/ballerina/stdlib/crypto/Constants.java +++ b/native/src/main/java/io/ballerina/stdlib/crypto/Constants.java @@ -132,6 +132,6 @@ private Constants() {} public static final String INPUT_STREAM_TO_ENCRYPT = "INPUT_STREAM_TO_ENCRYPT"; public static final String PIPED_INPUT_STREAM = "PIPED_INPUT_STREAM"; public static final String PIPED_OUTPUT_STREAM = "PIPED_OUTPUT_STREAM"; - public static final String ENCRYPTION_STARTED = "ENCRYPTION_STARTED"; + public static final String END_OF_INPUT_STREAM = "END_OF_INPUT_STREAM"; public static final String COMPRESSED_DATA_GENERATOR = "COMPRESSED_DATA_GENERATOR"; } diff --git a/native/src/main/java/io/ballerina/stdlib/crypto/PgpEncryptionGenerator.java b/native/src/main/java/io/ballerina/stdlib/crypto/PgpEncryptionGenerator.java index 99bfa05..94db696 100644 --- a/native/src/main/java/io/ballerina/stdlib/crypto/PgpEncryptionGenerator.java +++ b/native/src/main/java/io/ballerina/stdlib/crypto/PgpEncryptionGenerator.java @@ -39,8 +39,6 @@ import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; -import java.io.PipedInputStream; -import java.io.PipedOutputStream; import java.nio.file.Files; import java.nio.file.Path; import java.security.SecureRandom; @@ -114,10 +112,10 @@ private void encryptStream(OutputStream encryptOut, InputStream clearIn, InputSt public void encryptStream(InputStream publicKeyIn, BObject iteratorObj) throws IOException, PGPException { - OutputStream encryptOut = new PipedOutputStream(); + SequentialBufferedPipe pipe = new SequentialBufferedPipe(); + OutputStream encryptOut = pipe.getOutputStream(); iteratorObj.addNativeData(PIPED_OUTPUT_STREAM, encryptOut); - PipedInputStream pipedInputStream = new PipedInputStream((PipedOutputStream) encryptOut, - PgpEncryptionGenerator.BUFFER_SIZE); + InputStream pipedInputStream = pipe.getInputStream(); iteratorObj.addNativeData(PIPED_INPUT_STREAM, pipedInputStream); PGPCompressedDataGenerator compressedDataGenerator = new PGPCompressedDataGenerator(compressionAlgorithm); PGPEncryptedDataGenerator pgpEncryptedDataGenerator = new PGPEncryptedDataGenerator( diff --git a/native/src/main/java/io/ballerina/stdlib/crypto/SequentialBufferedPipe.java b/native/src/main/java/io/ballerina/stdlib/crypto/SequentialBufferedPipe.java new file mode 100644 index 0000000..c07d967 --- /dev/null +++ b/native/src/main/java/io/ballerina/stdlib/crypto/SequentialBufferedPipe.java @@ -0,0 +1,72 @@ +package io.ballerina.stdlib.crypto; + +import java.io.InputStream; +import java.io.OutputStream; +import java.util.LinkedList; +import java.util.Queue; + +/** + * Represents a pipe that can be used to connect an output stream to an input stream. + * This Pipe implementation assumes the output stream write and input stream read operations are done + * sequentially in the same thread and the output stream should be closed after writing is done. + * + * @since 2.8.0 + */ +public class SequentialBufferedPipe { + + Queue buffer = new LinkedList<>(); + boolean outputClosed = false; + + public InputStream getInputStream() { + return new InputStream() { + @Override + public int read() { + if (buffer.isEmpty()) { + if (outputClosed) { + return -1; + } + // This should not be reached with respect to the assumption + return 0; + } + return buffer.poll() & 0xFF; + } + + @Override + public int read(byte[] b, int off, int len) { + if (buffer.isEmpty()) { + if (outputClosed) { + return -1; + } + return 0; + } + int i = 0; + while (i < len && !buffer.isEmpty()) { + b[off + i] = buffer.poll(); + i++; + } + return i; + } + }; + } + + public OutputStream getOutputStream() { + return new OutputStream() { + @Override + public void write(int b) { + buffer.add((byte) b); + } + + @Override + public void write(byte[] b, int off, int len) { + for (int i = off; i < off + len; i++) { + buffer.add(b[i]); + } + } + + @Override + public void close() { + outputClosed = true; + } + }; + } +} diff --git a/native/src/main/java/io/ballerina/stdlib/crypto/nativeimpl/Encrypt.java b/native/src/main/java/io/ballerina/stdlib/crypto/nativeimpl/Encrypt.java index 0ded0ea..63a0538 100644 --- a/native/src/main/java/io/ballerina/stdlib/crypto/nativeimpl/Encrypt.java +++ b/native/src/main/java/io/ballerina/stdlib/crypto/nativeimpl/Encrypt.java @@ -44,7 +44,7 @@ import java.security.PrivateKey; import java.security.PublicKey; -import static io.ballerina.stdlib.crypto.Constants.ENCRYPTION_STARTED; +import static io.ballerina.stdlib.crypto.Constants.END_OF_INPUT_STREAM; import static io.ballerina.stdlib.crypto.Constants.INPUT_STREAM_TO_ENCRYPT; /** @@ -173,7 +173,7 @@ public static Object encryptStreamPgp(Environment environment, BStream inputBalS Boolean.parseBoolean(options.get(WITH_INTEGRITY_CHECK).toString()) ); BObject iteratorObj = ValueCreator.createObjectValue(ModuleUtils.getModule(), "EncryptedStreamIterator"); - iteratorObj.addNativeData(ENCRYPTION_STARTED, false); + iteratorObj.addNativeData(END_OF_INPUT_STREAM, false); iteratorObj.addNativeData(INPUT_STREAM_TO_ENCRYPT, inputStream); pgpEncryptionGenerator.encryptStream(publicKeyStream, iteratorObj); Type constrainedType = TypeCreator.createArrayType(PredefinedTypes.TYPE_BYTE); diff --git a/native/src/main/java/io/ballerina/stdlib/crypto/nativeimpl/StreamUtils.java b/native/src/main/java/io/ballerina/stdlib/crypto/nativeimpl/StreamUtils.java index 4282f98..ff9c0cb 100644 --- a/native/src/main/java/io/ballerina/stdlib/crypto/nativeimpl/StreamUtils.java +++ b/native/src/main/java/io/ballerina/stdlib/crypto/nativeimpl/StreamUtils.java @@ -28,12 +28,11 @@ import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; -import java.io.PipedInputStream; import java.util.Objects; import static io.ballerina.stdlib.crypto.Constants.COMPRESSED_DATA_GENERATOR; import static io.ballerina.stdlib.crypto.Constants.ENCRYPTED_OUTPUT_STREAM; -import static io.ballerina.stdlib.crypto.Constants.ENCRYPTION_STARTED; +import static io.ballerina.stdlib.crypto.Constants.END_OF_INPUT_STREAM; import static io.ballerina.stdlib.crypto.Constants.INPUT_STREAM_TO_ENCRYPT; import static io.ballerina.stdlib.crypto.Constants.PIPED_INPUT_STREAM; import static io.ballerina.stdlib.crypto.Constants.COMPRESSED_DATA_STREAM; @@ -86,16 +85,8 @@ public static Object readEncryptedStream(BObject iterator) { NativeData nativeData = getNativeData(iterator); try { - if (Boolean.FALSE.equals(nativeData.encryptionStarted())) { - iterator.addNativeData(ENCRYPTION_STARTED, true); - Thread writer = new Thread(() -> { - try { - writeToOutStream(iterator, nativeData.inputStream(), nativeData.outputStream()); - } catch (IOException e) { - throw new RuntimeException(e); - } - }); - writer.start(); + if (Boolean.FALSE.equals(nativeData.endOfStream())) { + writeToOutStream(iterator, nativeData.inputStream(), nativeData.outputStream()); } return readFromPipedStream(nativeData.pipedInStream()); } catch (IOException e) { @@ -117,23 +108,22 @@ private static NativeData getNativeData(BObject iterator) { } Object pipelinedInputStream = iterator.getNativeData(PIPED_INPUT_STREAM); - if (Objects.isNull(pipelinedInputStream) || - !(pipelinedInputStream instanceof PipedInputStream pipedInStream)) { + if (Objects.isNull(pipelinedInputStream) || !(pipelinedInputStream instanceof InputStream pipedInStream)) { throw CryptoUtils.createError(String.format(NATIVE_DATA_NOT_AVAILABLE_ERROR, PIPED_INPUT_STREAM)); } - Object encryptionStartedObj = iterator.getNativeData(ENCRYPTION_STARTED); - if (Objects.isNull(encryptionStartedObj) || !(encryptionStartedObj instanceof Boolean encryptionStarted)) { - throw CryptoUtils.createError(String.format(NATIVE_DATA_NOT_AVAILABLE_ERROR, ENCRYPTION_STARTED)); + Object endOfInputStream = iterator.getNativeData(END_OF_INPUT_STREAM); + if (Objects.isNull(endOfInputStream) || !(endOfInputStream instanceof Boolean endOfStream)) { + throw CryptoUtils.createError(String.format(NATIVE_DATA_NOT_AVAILABLE_ERROR, END_OF_INPUT_STREAM)); } - return new NativeData(inputStream, outputStream, pipedInStream, encryptionStarted); + return new NativeData(inputStream, outputStream, pipedInStream, endOfStream); } - private record NativeData(InputStream inputStream, OutputStream outputStream, PipedInputStream pipedInStream, - Boolean encryptionStarted) { + private record NativeData(InputStream inputStream, OutputStream outputStream, InputStream pipedInStream, + Boolean endOfStream) { } - private static BArray readFromPipedStream(PipedInputStream pipedInStream) throws IOException { + private static BArray readFromPipedStream(InputStream pipedInStream) throws IOException { byte[] pipelinedBuffer = new byte[BUFFER_SIZE]; int pipelinedIn = pipedInStream.read(pipelinedBuffer); if (pipelinedIn == -1) { @@ -150,11 +140,14 @@ private static BArray readFromPipedStream(PipedInputStream pipedInStream) throws private static void writeToOutStream(BObject iterator, InputStream inputStream, OutputStream outputStream) throws IOException { byte[] inputBuffer = new byte[BUFFER_SIZE]; - int len; - while ((len = inputStream.read(inputBuffer)) > 0) { - outputStream.write(inputBuffer, 0, len); + int result = inputStream.read(inputBuffer); + if (result == -1) { + iterator.addNativeData(END_OF_INPUT_STREAM, true); + closeEncryptedSourceStreams(iterator); + } + if (result > 0) { + outputStream.write(inputBuffer, 0, result); } - closeEncryptedSourceStreams(iterator); } public static void closeDecryptedStream(BObject iterator) throws BError { From b635c453c2bd59c2959be48717570d63217b4fc5 Mon Sep 17 00:00:00 2001 From: TharmiganK Date: Wed, 9 Oct 2024 12:07:55 +0530 Subject: [PATCH 23/42] Remove PGP file APIs --- ballerina/encrypt_decrypt.bal | 33 ------------------- .../stdlib/crypto/PgpDecryptionGenerator.java | 8 ----- .../stdlib/crypto/PgpEncryptionGenerator.java | 9 ----- .../stdlib/crypto/nativeimpl/Decrypt.java | 21 ------------ .../stdlib/crypto/nativeimpl/Encrypt.java | 25 -------------- 5 files changed, 96 deletions(-) diff --git a/ballerina/encrypt_decrypt.bal b/ballerina/encrypt_decrypt.bal index fa19096..ba52052 100644 --- a/ballerina/encrypt_decrypt.bal +++ b/ballerina/encrypt_decrypt.bal @@ -260,22 +260,6 @@ public isolated function encryptPgp(byte[] plainText, string publicKeyPath, *Opt 'class: "io.ballerina.stdlib.crypto.nativeimpl.Encrypt" } external; -# Writes the PGP-encrypted value of the content given in the input file to a file specified by the output file path. -# If the output file already exists, it will be overwritten. -# ```ballerina -# check crypto:encryptPgpAsFile("input.txt", "public_key.asc", "output.txt"); -# ``` -# -# + inputFilePath - Path to the input file -# + publicKeyPath - Path to the public key -# + outputFilePath - Path to the output file -# + options - PGP encryption options -# + return - A `crypto:Error` will be returned if the process fails -public isolated function encryptPgpAsFile(string inputFilePath, string publicKeyPath, string outputFilePath, - *Options options) returns Error? = @java:Method { - 'class: "io.ballerina.stdlib.crypto.nativeimpl.Encrypt" -} external; - # Returns the PGP-encrypted stream of the content given in the input stream. # ```ballerina # stream inputStream = check io:fileReadBlocksAsStream("input.txt"); @@ -309,23 +293,6 @@ public isolated function decryptPgp(byte[] cipherText, string privateKeyPath, by 'class: "io.ballerina.stdlib.crypto.nativeimpl.Decrypt" } external; -# Writes the PGP-decrypted value of the content given in the input file to a file specified by the output file path. -# If the output file already exists, it will be overwritten. -# ```ballerina -# byte[] passphrase = check io:fileReadBytes("pass_phrase.txt"); -# check crypto:decryptPgpAsFile("input.txt", "private_key.asc", passphrase, "output.txt"); -# ``` -# -# + inputFilePath - Path to the input file -# + privateKeyPath - Path to the private key -# + passphrase - passphrase of the private key -# + outputFilePath - Path to the output file -# + return - A `crypto:Error` will be returned if the process fails -public isolated function decryptPgpAsFile(string inputFilePath, string privateKeyPath, byte[] passphrase, - string outputFilePath) returns Error? = @java:Method { - 'class: "io.ballerina.stdlib.crypto.nativeimpl.Decrypt" -} external; - # Returns the PGP-decrypted stream of the content given in the input stream. # ```ballerina # byte[] passphrase = check io:fileReadBytes("pass_phrase.txt"); diff --git a/native/src/main/java/io/ballerina/stdlib/crypto/PgpDecryptionGenerator.java b/native/src/main/java/io/ballerina/stdlib/crypto/PgpDecryptionGenerator.java index 5bd939a..0613d1f 100644 --- a/native/src/main/java/io/ballerina/stdlib/crypto/PgpDecryptionGenerator.java +++ b/native/src/main/java/io/ballerina/stdlib/crypto/PgpDecryptionGenerator.java @@ -44,8 +44,6 @@ import java.io.InputStream; import java.io.OutputStream; import java.nio.charset.StandardCharsets; -import java.nio.file.Files; -import java.nio.file.Path; import java.security.Security; import java.util.Iterator; import java.util.Objects; @@ -159,12 +157,6 @@ public Object decrypt(byte[] encryptedBytes) throws PGPException, IOException { } } - public void decrypt(InputStream encryptedIn, String outputPath) throws PGPException, IOException { - try (OutputStream outputStream = Files.newOutputStream(Path.of(outputPath))) { - decryptStream(encryptedIn, outputStream); - } - } - private static void decrypt(OutputStream clearOut, PGPPrivateKey pgpPrivateKey, PGPPublicKeyEncryptedData publicKeyEncryptedData) throws IOException, PGPException { PublicKeyDataDecryptorFactory decryptorFactory = new JcePublicKeyDataDecryptorFactoryBuilder() diff --git a/native/src/main/java/io/ballerina/stdlib/crypto/PgpEncryptionGenerator.java b/native/src/main/java/io/ballerina/stdlib/crypto/PgpEncryptionGenerator.java index 94db696..f2145c7 100644 --- a/native/src/main/java/io/ballerina/stdlib/crypto/PgpEncryptionGenerator.java +++ b/native/src/main/java/io/ballerina/stdlib/crypto/PgpEncryptionGenerator.java @@ -39,8 +39,6 @@ import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; -import java.nio.file.Files; -import java.nio.file.Path; import java.security.SecureRandom; import java.security.Security; import java.time.LocalDateTime; @@ -149,13 +147,6 @@ public Object encrypt(byte[] clearData, InputStream publicKeyIn) throws PGPExcep } } - public void encrypt(InputStream inputStream, InputStream publicKeyIn, String outputPath) - throws PGPException, IOException { - try (OutputStream outputStream = Files.newOutputStream(Path.of(outputPath))) { - encryptStream(outputStream, inputStream, publicKeyIn); - } - } - private static PGPPublicKey getPublicKey(InputStream keyInputStream) throws IOException, PGPException { PGPPublicKeyRingCollection pgpPublicKeyRings = new PGPPublicKeyRingCollection( PGPUtil.getDecoderStream(keyInputStream), new JcaKeyFingerprintCalculator()); diff --git a/native/src/main/java/io/ballerina/stdlib/crypto/nativeimpl/Decrypt.java b/native/src/main/java/io/ballerina/stdlib/crypto/nativeimpl/Decrypt.java index b433372..428cda3 100644 --- a/native/src/main/java/io/ballerina/stdlib/crypto/nativeimpl/Decrypt.java +++ b/native/src/main/java/io/ballerina/stdlib/crypto/nativeimpl/Decrypt.java @@ -113,27 +113,6 @@ public static Object decryptPgp(BArray cipherTextValue, BString privateKeyPath, } } - public static Object decryptPgpAsFile(BString inputFilePath, BString privateKeyPath, BArray passphrase, - BString outputFilePath) { - byte[] passphraseInBytes = passphrase.getBytes(); - byte[] privateKey; - try { - privateKey = Files.readAllBytes(Path.of(privateKeyPath.toString())); - } catch (IOException e) { - return CryptoUtils.createError(ERROR_OCCURRED_WHILE_READING_PRIVATE_KEY + e.getMessage()); - } - - try (InputStream keyStream = new ByteArrayInputStream(privateKey); - InputStream cipherTextStream = Files.newInputStream(Path.of(inputFilePath.toString())) - ) { - PgpDecryptionGenerator pgpDecryptionGenerator = new PgpDecryptionGenerator(keyStream, passphraseInBytes); - pgpDecryptionGenerator.decrypt(cipherTextStream, outputFilePath.getValue()); - return null; - } catch (IOException | PGPException e) { - return CryptoUtils.createError(ERROR_OCCURRED_WHILE_PGP_DECRYPT + e.getMessage()); - } - } - public static Object decryptStreamPgp(Environment environment, BStream inputBalStream, BString privateKeyPath, BArray passphrase) { byte[] passphraseInBytes = passphrase.getBytes(); diff --git a/native/src/main/java/io/ballerina/stdlib/crypto/nativeimpl/Encrypt.java b/native/src/main/java/io/ballerina/stdlib/crypto/nativeimpl/Encrypt.java index 63a0538..aa2870a 100644 --- a/native/src/main/java/io/ballerina/stdlib/crypto/nativeimpl/Encrypt.java +++ b/native/src/main/java/io/ballerina/stdlib/crypto/nativeimpl/Encrypt.java @@ -130,31 +130,6 @@ public static Object encryptPgp(BArray plainTextValue, BString publicKeyPath, BM } } - public static Object encryptPgpAsFile(BString inputFilePath, BString publicKeyPath, BString outputFilePath, - BMap options) { - byte[] publicKey; - try { - publicKey = Files.readAllBytes(Path.of(publicKeyPath.toString())); - } catch (IOException e) { - return CryptoUtils.createError(ERROR_OCCURRED_WHILE_READING_PUBLIC_KEY + e.getMessage()); - } - - try (InputStream publicKeyStream = new ByteArrayInputStream(publicKey); - InputStream inputStream = Files.newInputStream(Path.of(inputFilePath.toString())) - ) { - PgpEncryptionGenerator pgpEncryptionGenerator = new PgpEncryptionGenerator( - Integer.parseInt(options.get(COMPRESSION_ALGORITHM).toString()), - Integer.parseInt(options.get(SYMMETRIC_KEY_ALGORITHM).toString()), - Boolean.parseBoolean(options.get(ARMOR).toString()), - Boolean.parseBoolean(options.get(WITH_INTEGRITY_CHECK).toString()) - ); - pgpEncryptionGenerator.encrypt(inputStream, publicKeyStream, outputFilePath.getValue()); - return null; - } catch (IOException | PGPException e) { - return CryptoUtils.createError(ERROR_OCCURRED_WHILE_PGP_ENCRYPT + e.getMessage()); - } - } - public static Object encryptStreamPgp(Environment environment, BStream inputBalStream, BString publicKeyPath, BMap options) { byte[] publicKey; From 9cdd933b58afaf3d2fe6cc511b7fe2bc1e65dd19 Mon Sep 17 00:00:00 2001 From: TharmiganK Date: Wed, 9 Oct 2024 12:08:05 +0530 Subject: [PATCH 24/42] Fix test cases --- ballerina/tests/encrypt_decrypt_pgp_test.bal | 62 +++++++++++++------- 1 file changed, 42 insertions(+), 20 deletions(-) diff --git a/ballerina/tests/encrypt_decrypt_pgp_test.bal b/ballerina/tests/encrypt_decrypt_pgp_test.bal index 562ebe1..8e79ce5 100644 --- a/ballerina/tests/encrypt_decrypt_pgp_test.bal +++ b/ballerina/tests/encrypt_decrypt_pgp_test.bal @@ -67,9 +67,17 @@ isolated function testNegativeEncryptAndDecryptWithPgpInvalidPassphrase() return } isolated function testEncryptAndDecryptFileWithPgp() returns error? { byte[] passphrase = "qCr3bv@5mj5n4eY".toBytes(); - check encryptPgpAsFile(SAMPLE_TEXT, PGP_PUBLIC_KEY_PATH, TARGET_ENCRYPTION_OUTPUT); - check decryptPgpAsFile(TARGET_ENCRYPTION_OUTPUT, PGP_PRIVATE_KEY_PATH, passphrase, TARGET_DECRYPTION_OUTPUT); - test:assertTrue(check isSameFileContent(SAMPLE_TEXT, TARGET_DECRYPTION_OUTPUT)); + stream inputStream = check io:fileReadBlocksAsStream(SAMPLE_TEXT); + stream encryptedStream = check encryptStreamPgp(inputStream, PGP_PUBLIC_KEY_PATH); + stream decryptedStream = check decryptStreamPgp(encryptedStream, PGP_PRIVATE_KEY_PATH, passphrase); + + byte[] expected = check io:fileReadBytes(SAMPLE_TEXT); + byte[] actual = []; + check from byte[] bytes in decryptedStream + do { + actual.push(...bytes); + }; + test:assertEquals(actual, expected); } @test:Config { @@ -77,9 +85,17 @@ isolated function testEncryptAndDecryptFileWithPgp() returns error? { } isolated function testEncryptAndDecryptFileWithPgpWithOptions() returns error? { byte[] passphrase = "qCr3bv@5mj5n4eY".toBytes(); - check encryptPgpAsFile(SAMPLE_TEXT, PGP_PUBLIC_KEY_PATH, TARGET_ENCRYPTION_OUTPUT, symmetricKeyAlgorithm = AES_128, armor = false); - check decryptPgpAsFile(TARGET_ENCRYPTION_OUTPUT, PGP_PRIVATE_KEY_PATH, passphrase, TARGET_DECRYPTION_OUTPUT); - test:assertTrue(check isSameFileContent(SAMPLE_TEXT, TARGET_DECRYPTION_OUTPUT)); + stream inputStream = check io:fileReadBlocksAsStream(SAMPLE_TEXT); + stream encryptedStream = check encryptStreamPgp(inputStream, PGP_PUBLIC_KEY_PATH, symmetricKeyAlgorithm = AES_128, armor = false); + stream decryptedStream = check decryptStreamPgp(encryptedStream, PGP_PRIVATE_KEY_PATH, passphrase); + + byte[] expected = check io:fileReadBytes(SAMPLE_TEXT); + byte[] actual = []; + check from byte[] bytes in decryptedStream + do { + actual.push(...bytes); + }; + test:assertEquals(actual, expected); } @test:Config { @@ -87,11 +103,17 @@ isolated function testEncryptAndDecryptFileWithPgpWithOptions() returns error? { } isolated function testNegativeEncryptAndDecryptFileWithPgpInvalidPrivateKey() returns error? { byte[] passphrase = "p7S5@T2MRFD9TQb".toBytes(); - check encryptPgpAsFile(SAMPLE_TEXT, PGP_PUBLIC_KEY_PATH, TARGET_ENCRYPTION_OUTPUT); - error? err = decryptPgpAsFile(TARGET_ENCRYPTION_OUTPUT, PGP_INVALID_PRIVATE_KEY_PATH, passphrase, TARGET_DECRYPTION_OUTPUT); - if err is Error { - test:assertEquals(err.message(), "Error occurred while PGP decrypt: Could not Extract private key"); + stream inputStream = check io:fileReadBlocksAsStream(SAMPLE_TEXT); + stream encryptedStream = check encryptStreamPgp(inputStream, PGP_PUBLIC_KEY_PATH, symmetricKeyAlgorithm = AES_128, armor = false); + stream|Error result = check decryptStreamPgp(encryptedStream, PGP_INVALID_PRIVATE_KEY_PATH, passphrase); + if result is Error { + check encryptedStream.close(); + check inputStream.close(); + test:assertEquals(result.message(), "Error occurred while PGP decrypt: Could not Extract private key"); } else { + check encryptedStream.close(); + check inputStream.close(); + check result.close(); test:assertFail("Should return a crypto Error"); } } @@ -101,18 +123,18 @@ isolated function testNegativeEncryptAndDecryptFileWithPgpInvalidPrivateKey() re } isolated function testNegativeEncryptAndDecryptFileWithPgpInvalidPassphrase() returns error? { byte[] passphrase = "p7S5@T2MRFD9TQb".toBytes(); - check encryptPgpAsFile(SAMPLE_TEXT, PGP_PUBLIC_KEY_PATH, TARGET_ENCRYPTION_OUTPUT); - error? err = decryptPgpAsFile(TARGET_ENCRYPTION_OUTPUT, PGP_PRIVATE_KEY_PATH, passphrase, TARGET_DECRYPTION_OUTPUT); - if err is Error { - test:assertEquals(err.message(), + stream inputStream = check io:fileReadBlocksAsStream(SAMPLE_TEXT); + stream encryptedStream = check encryptStreamPgp(inputStream, PGP_PUBLIC_KEY_PATH, symmetricKeyAlgorithm = AES_128, armor = false); + stream|Error result = check decryptStreamPgp(encryptedStream, PGP_PRIVATE_KEY_PATH, passphrase); + if result is Error { + check encryptedStream.close(); + check inputStream.close(); + test:assertEquals(result.message(), "Error occurred while PGP decrypt: checksum mismatch at in checksum of 20 bytes"); } else { + check encryptedStream.close(); + check inputStream.close(); + check result.close(); test:assertFail("Should return a crypto Error"); } } - -isolated function isSameFileContent(string inputFilePath, string outputFilePath) returns boolean|error { - byte[] input = check io:fileReadBytes(inputFilePath); - byte[] output = check io:fileReadBytes(outputFilePath); - return input == output; -} From 29333dc112c3e78477d433d55537e0893e5de8ff Mon Sep 17 00:00:00 2001 From: TharmiganK Date: Wed, 9 Oct 2024 12:09:44 +0530 Subject: [PATCH 25/42] Update change log --- changelog.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/changelog.md b/changelog.md index 57b2a5e..1abac4d 100644 --- a/changelog.md +++ b/changelog.md @@ -7,7 +7,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), ### Added -- [Introduce new APIs to support PGP encryption and decryption with files](https://github.com/ballerina-platform/ballerina-library/issues/7064) +- [Introduce new APIs to support PGP encryption and decryption with streams](https://github.com/ballerina-platform/ballerina-library/issues/7064) ## [2.7.2] - 2024-05-30 From f71ebb7f62cda810436911d650038e3a6053c5ff Mon Sep 17 00:00:00 2001 From: TharmiganK Date: Wed, 9 Oct 2024 12:12:17 +0530 Subject: [PATCH 26/42] Update spec --- docs/spec/spec.md | 23 ++++++++--------------- 1 file changed, 8 insertions(+), 15 deletions(-) diff --git a/docs/spec/spec.md b/docs/spec/spec.md index 8a1f6b7..16da3c6 100644 --- a/docs/spec/spec.md +++ b/docs/spec/spec.md @@ -532,15 +532,12 @@ string publicKeyPath = "/path/to/publickey.asc"; byte[] cipherText = check crypto:encryptPgp(data, publicKeyPath, armor = false); ``` -In addition to the above, the following API can be used to read a content from a file, encrypt it using the PGP public -key and write the encrypted content to the file specified. +In addition to the above, the following API can be used to read a content from a stream, encrypt it using the PGP public +key and return an encrypted stream ```ballerina -string inputFilePath = "/path/to/input.txt"; -string outputFilePath = "/path/to/output.txt"; -string publicKeyPath = "/path/to/publickey.asc"; - -check crypto:encryptPgpAsFile(inputFilePath, publicKeyPath, outputFilePath); +stream inputStream = check io:fileReadBlocksAsStream("input.txt"); +stream|Error encryptedStream = crypto:encryptStreamPgp(inputStream, "public_key.asc"); ``` ### 5.2. [Decryption](#52-decryption) @@ -630,16 +627,12 @@ byte[] cipherText = check crypto:encryptPgp(data, publicKeyPath); byte[] plainText = check crypto:decryptPgp(cipherText, privateKeyPath, passPhrase.toBytes()); ``` -In addition to the above, the following API can be used to read an encrypted content from a file, decrypt it using the -PGP private key and passphrase and write the decrypted content to the file specified. +In addition to the above, the following API can be used to read an encrypted content from a stream, decrypt it using the +PGP private key and passphrase and return a decrypted stream. ```ballerina -string inputFilePath = "/path/to/input.txt"; -string outputFilePath = "/path/to/output.txt"; -string privateKeyPath = "/path/to/privatekey.asc"; -string passPhrase = "passphrase"; - -check crypto:decryptPgpAsFile(inputFilePath, privateKeyPath, passPhrase.toBytes(), outputFilePath); +stream inputStream = check io:fileReadBlocksAsStream("pgb_encrypted.txt"); +stream|Error decryptedStream = crypto:decryptStreamPgp(inputStream, "private_key.asc", passphrase); ``` ## 6. [Sign and Verify](#6-sign-and-verify) From 91697210486e3ef0892cb0466311cd2a09b58547 Mon Sep 17 00:00:00 2001 From: TharmiganK Date: Wed, 9 Oct 2024 13:14:19 +0530 Subject: [PATCH 27/42] Update ballerina invoke call --- gradle.properties | 2 +- .../stdlib/crypto/BallerinaInputStream.java | 39 ++----------------- 2 files changed, 5 insertions(+), 36 deletions(-) diff --git a/gradle.properties b/gradle.properties index b8f845a..d94ed20 100644 --- a/gradle.properties +++ b/gradle.properties @@ -10,6 +10,6 @@ researchgateReleaseVersion=2.8.0 ballerinaGradlePluginVersion=2.0.1 nativeImageVersion=22.2.0 -ballerinaLangVersion=2201.9.0 +ballerinaLangVersion=2201.10.0-20240926-231800-8a5a4343 stdlibTimeVersion=2.4.0 stdlibIoVersion=1.6.1 diff --git a/native/src/main/java/io/ballerina/stdlib/crypto/BallerinaInputStream.java b/native/src/main/java/io/ballerina/stdlib/crypto/BallerinaInputStream.java index 335f79b..909367e 100644 --- a/native/src/main/java/io/ballerina/stdlib/crypto/BallerinaInputStream.java +++ b/native/src/main/java/io/ballerina/stdlib/crypto/BallerinaInputStream.java @@ -18,7 +18,6 @@ package io.ballerina.stdlib.crypto; import io.ballerina.runtime.api.Environment; -import io.ballerina.runtime.api.async.Callback; import io.ballerina.runtime.api.utils.StringUtils; import io.ballerina.runtime.api.values.BArray; import io.ballerina.runtime.api.values.BError; @@ -29,7 +28,6 @@ import java.io.InputStream; import java.nio.ByteBuffer; import java.util.Objects; -import java.util.concurrent.CountDownLatch; /** * Represents a Ballerina stream as an {@link InputStream}. @@ -77,47 +75,18 @@ public int read() throws IOException { @Override public void close() throws IOException { - Object result = callBallerinaFunction(BAL_STREAM_CLOSE, ERROR_OCCURRED_WHILE_CLOSING_THE_STREAM); + Object result = callBalStreamMethod(BAL_STREAM_CLOSE); if (result instanceof BError bError) { throw new IOException((bError).getMessage()); } } public Object getNext() { - return callBallerinaFunction(BAL_STREAM_NEXT, ERROR_OCCURRED_WHILE_READING_THE_STREAM); + return callBalStreamMethod(BAL_STREAM_NEXT); } - private Object callBallerinaFunction(String functionName, String message) { - final Object[] nextResult = new Object[1]; - CountDownLatch countDownLatch = new CountDownLatch(1); - Callback returnCallback = new StreamCallback(message, nextResult, countDownLatch); - - environment.getRuntime().invokeMethodAsyncSequentially(ballerinaStream.getIteratorObj(), functionName, null, - null, returnCallback, null, null); - try { - countDownLatch.await(); - } catch (InterruptedException exception) { - Thread.currentThread().interrupt(); - return CryptoUtils.createError(INTERRUPTED_ERROR_WHILE_READING_THE_STREAM); - } - return nextResult[0]; - } - - private record StreamCallback(String message, Object[] nextResult, - CountDownLatch countDownLatch) implements Callback { - - @Override - public void notifySuccess(Object result) { - nextResult[0] = result; - countDownLatch.countDown(); - } - - @Override - public void notifyFailure(BError bError) { - BError error = CryptoUtils.createError(String.format(ERR_MSG_FORMAT, message, bError.getMessage())); - nextResult[0] = error; - countDownLatch.countDown(); - } + private Object callBalStreamMethod(String functionName) { + return environment.getRuntime().call(ballerinaStream.getIteratorObj(), functionName); } @Override From 1ba983dc7bf0229338f503b0f9989ac010e2f95d Mon Sep 17 00:00:00 2001 From: TharmiganK Date: Wed, 9 Oct 2024 13:14:48 +0530 Subject: [PATCH 28/42] [Automated] Update the native jar versions --- ballerina/Ballerina.toml | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/ballerina/Ballerina.toml b/ballerina/Ballerina.toml index 1ce6ae2..93cdd31 100644 --- a/ballerina/Ballerina.toml +++ b/ballerina/Ballerina.toml @@ -9,34 +9,34 @@ icon = "icon.png" license = ["Apache-2.0"] distribution = "2201.9.0" -[platform.java17] +[platform.java21] graalvmCompatible = true -[[platform.java17.dependency]] +[[platform.java21.dependency]] groupId = "io.ballerina.stdlib" artifactId = "crypto-native" version = "2.8.0" path = "../native/build/libs/crypto-native-2.8.0-SNAPSHOT.jar" -[[platform.java17.dependency]] +[[platform.java21.dependency]] groupId = "org.bouncycastle" artifactId = "bcpkix-jdk18on" version = "1.78" path = "./lib/bcpkix-jdk18on-1.78.jar" -[[platform.java17.dependency]] +[[platform.java21.dependency]] groupId = "org.bouncycastle" artifactId = "bcprov-jdk18on" version = "1.78" path = "./lib/bcprov-jdk18on-1.78.jar" -[[platform.java17.dependency]] +[[platform.java21.dependency]] groupId = "org.bouncycastle" artifactId = "bcutil-jdk18on" version = "1.78" path = "./lib/bcutil-jdk18on-1.78.jar" -[[platform.java17.dependency]] +[[platform.java21.dependency]] groupId = "org.bouncycastle" artifactId = "bcpg-jdk18on" version = "1.78" From 132cd3154ae2712fd31dd25fd3fa9bf045719398 Mon Sep 17 00:00:00 2001 From: TharmiganK Date: Wed, 9 Oct 2024 13:15:29 +0530 Subject: [PATCH 29/42] [Automated] Update the native jar versions --- ballerina/Dependencies.toml | 117 ------------------------------------ 1 file changed, 117 deletions(-) diff --git a/ballerina/Dependencies.toml b/ballerina/Dependencies.toml index 3cc7bc4..e69de29 100644 --- a/ballerina/Dependencies.toml +++ b/ballerina/Dependencies.toml @@ -1,117 +0,0 @@ -# AUTO-GENERATED FILE. DO NOT MODIFY. - -# This file is auto-generated by Ballerina for managing dependency versions. -# It should not be modified by hand. - -[ballerina] -dependencies-toml-version = "2" -distribution-version = "2201.9.0" - -[[package]] -org = "ballerina" -name = "crypto" -version = "2.8.0" -dependencies = [ - {org = "ballerina", name = "io"}, - {org = "ballerina", name = "jballerina.java"}, - {org = "ballerina", name = "lang.array"}, - {org = "ballerina", name = "test"}, - {org = "ballerina", name = "time"} -] -modules = [ - {org = "ballerina", packageName = "crypto", moduleName = "crypto"} -] - -[[package]] -org = "ballerina" -name = "io" -version = "1.6.1" -scope = "testOnly" -dependencies = [ - {org = "ballerina", name = "jballerina.java"}, - {org = "ballerina", name = "lang.value"} -] -modules = [ - {org = "ballerina", packageName = "io", moduleName = "io"} -] - -[[package]] -org = "ballerina" -name = "jballerina.java" -version = "0.0.0" -modules = [ - {org = "ballerina", packageName = "jballerina.java", moduleName = "jballerina.java"} -] - -[[package]] -org = "ballerina" -name = "lang.__internal" -version = "0.0.0" -scope = "testOnly" -dependencies = [ - {org = "ballerina", name = "jballerina.java"}, - {org = "ballerina", name = "lang.object"} -] - -[[package]] -org = "ballerina" -name = "lang.array" -version = "0.0.0" -scope = "testOnly" -dependencies = [ - {org = "ballerina", name = "jballerina.java"}, - {org = "ballerina", name = "lang.__internal"} -] -modules = [ - {org = "ballerina", packageName = "lang.array", moduleName = "lang.array"} -] - -[[package]] -org = "ballerina" -name = "lang.error" -version = "0.0.0" -scope = "testOnly" -dependencies = [ - {org = "ballerina", name = "jballerina.java"} -] - -[[package]] -org = "ballerina" -name = "lang.object" -version = "0.0.0" -scope = "testOnly" - -[[package]] -org = "ballerina" -name = "lang.value" -version = "0.0.0" -scope = "testOnly" -dependencies = [ - {org = "ballerina", name = "jballerina.java"} -] - -[[package]] -org = "ballerina" -name = "test" -version = "0.0.0" -scope = "testOnly" -dependencies = [ - {org = "ballerina", name = "jballerina.java"}, - {org = "ballerina", name = "lang.array"}, - {org = "ballerina", name = "lang.error"} -] -modules = [ - {org = "ballerina", packageName = "test", moduleName = "test"} -] - -[[package]] -org = "ballerina" -name = "time" -version = "2.4.0" -dependencies = [ - {org = "ballerina", name = "jballerina.java"} -] -modules = [ - {org = "ballerina", packageName = "time", moduleName = "time"} -] - From a51aa97ea8287b32006bfa15740355bea6732020 Mon Sep 17 00:00:00 2001 From: TharmiganK Date: Wed, 9 Oct 2024 13:20:06 +0530 Subject: [PATCH 30/42] [Automated] Update the native jar versions --- ballerina/Dependencies.toml | 117 ++++++++++++++++++++++++++++++++++++ 1 file changed, 117 insertions(+) diff --git a/ballerina/Dependencies.toml b/ballerina/Dependencies.toml index e69de29..088071e 100644 --- a/ballerina/Dependencies.toml +++ b/ballerina/Dependencies.toml @@ -0,0 +1,117 @@ +# AUTO-GENERATED FILE. DO NOT MODIFY. + +# This file is auto-generated by Ballerina for managing dependency versions. +# It should not be modified by hand. + +[ballerina] +dependencies-toml-version = "2" +distribution-version = "2201.10.0-20240926-231800-8a5a4343" + +[[package]] +org = "ballerina" +name = "crypto" +version = "2.8.0" +dependencies = [ + {org = "ballerina", name = "io"}, + {org = "ballerina", name = "jballerina.java"}, + {org = "ballerina", name = "lang.array"}, + {org = "ballerina", name = "test"}, + {org = "ballerina", name = "time"} +] +modules = [ + {org = "ballerina", packageName = "crypto", moduleName = "crypto"} +] + +[[package]] +org = "ballerina" +name = "io" +version = "1.6.2" +scope = "testOnly" +dependencies = [ + {org = "ballerina", name = "jballerina.java"}, + {org = "ballerina", name = "lang.value"} +] +modules = [ + {org = "ballerina", packageName = "io", moduleName = "io"} +] + +[[package]] +org = "ballerina" +name = "jballerina.java" +version = "0.0.0" +modules = [ + {org = "ballerina", packageName = "jballerina.java", moduleName = "jballerina.java"} +] + +[[package]] +org = "ballerina" +name = "lang.__internal" +version = "0.0.0" +scope = "testOnly" +dependencies = [ + {org = "ballerina", name = "jballerina.java"}, + {org = "ballerina", name = "lang.object"} +] + +[[package]] +org = "ballerina" +name = "lang.array" +version = "0.0.0" +scope = "testOnly" +dependencies = [ + {org = "ballerina", name = "jballerina.java"}, + {org = "ballerina", name = "lang.__internal"} +] +modules = [ + {org = "ballerina", packageName = "lang.array", moduleName = "lang.array"} +] + +[[package]] +org = "ballerina" +name = "lang.error" +version = "0.0.0" +scope = "testOnly" +dependencies = [ + {org = "ballerina", name = "jballerina.java"} +] + +[[package]] +org = "ballerina" +name = "lang.object" +version = "0.0.0" +scope = "testOnly" + +[[package]] +org = "ballerina" +name = "lang.value" +version = "0.0.0" +scope = "testOnly" +dependencies = [ + {org = "ballerina", name = "jballerina.java"} +] + +[[package]] +org = "ballerina" +name = "test" +version = "0.0.0" +scope = "testOnly" +dependencies = [ + {org = "ballerina", name = "jballerina.java"}, + {org = "ballerina", name = "lang.array"}, + {org = "ballerina", name = "lang.error"} +] +modules = [ + {org = "ballerina", packageName = "test", moduleName = "test"} +] + +[[package]] +org = "ballerina" +name = "time" +version = "2.5.1" +dependencies = [ + {org = "ballerina", name = "jballerina.java"} +] +modules = [ + {org = "ballerina", packageName = "time", moduleName = "time"} +] + From 7139227d6d59d0a3bd17ae5ad06dfb29df5d138d Mon Sep 17 00:00:00 2001 From: TharmiganK Date: Wed, 9 Oct 2024 14:08:24 +0530 Subject: [PATCH 31/42] Update dependency versions supported with Java 21 --- gradle.properties | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/gradle.properties b/gradle.properties index cadf707..7a2f95c 100644 --- a/gradle.properties +++ b/gradle.properties @@ -11,5 +11,5 @@ ballerinaGradlePluginVersion=2.0.1 nativeImageVersion=22.2.0 ballerinaLangVersion=2201.10.0-20240926-231800-8a5a4343 -stdlibTimeVersion=2.4.0 -stdlibIoVersion=1.6.1 +stdlibTimeVersion=2.5.1-20240930-120200-e59222b +stdlibIoVersion=1.6.2-20240928-084100-656404f From 43b390354407e693315bccf7d6975dab6b0350ab Mon Sep 17 00:00:00 2001 From: TharmiganK Date: Wed, 9 Oct 2024 14:08:37 +0530 Subject: [PATCH 32/42] Rename test functions --- ballerina/tests/encrypt_decrypt_pgp_test.bal | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/ballerina/tests/encrypt_decrypt_pgp_test.bal b/ballerina/tests/encrypt_decrypt_pgp_test.bal index 8e79ce5..c9188d7 100644 --- a/ballerina/tests/encrypt_decrypt_pgp_test.bal +++ b/ballerina/tests/encrypt_decrypt_pgp_test.bal @@ -65,7 +65,7 @@ isolated function testNegativeEncryptAndDecryptWithPgpInvalidPassphrase() return @test:Config { serialExecution: true } -isolated function testEncryptAndDecryptFileWithPgp() returns error? { +isolated function testEncryptAndDecryptStreamWithPgp() returns error? { byte[] passphrase = "qCr3bv@5mj5n4eY".toBytes(); stream inputStream = check io:fileReadBlocksAsStream(SAMPLE_TEXT); stream encryptedStream = check encryptStreamPgp(inputStream, PGP_PUBLIC_KEY_PATH); @@ -83,7 +83,7 @@ isolated function testEncryptAndDecryptFileWithPgp() returns error? { @test:Config { serialExecution: true } -isolated function testEncryptAndDecryptFileWithPgpWithOptions() returns error? { +isolated function testEncryptAndDecryptStreamWithPgpWithOptions() returns error? { byte[] passphrase = "qCr3bv@5mj5n4eY".toBytes(); stream inputStream = check io:fileReadBlocksAsStream(SAMPLE_TEXT); stream encryptedStream = check encryptStreamPgp(inputStream, PGP_PUBLIC_KEY_PATH, symmetricKeyAlgorithm = AES_128, armor = false); @@ -101,11 +101,11 @@ isolated function testEncryptAndDecryptFileWithPgpWithOptions() returns error? { @test:Config { serialExecution: true } -isolated function testNegativeEncryptAndDecryptFileWithPgpInvalidPrivateKey() returns error? { +isolated function testNegativeEncryptAndDecryptStreamWithPgpInvalidPrivateKey() returns error? { byte[] passphrase = "p7S5@T2MRFD9TQb".toBytes(); stream inputStream = check io:fileReadBlocksAsStream(SAMPLE_TEXT); stream encryptedStream = check encryptStreamPgp(inputStream, PGP_PUBLIC_KEY_PATH, symmetricKeyAlgorithm = AES_128, armor = false); - stream|Error result = check decryptStreamPgp(encryptedStream, PGP_INVALID_PRIVATE_KEY_PATH, passphrase); + stream|Error result = decryptStreamPgp(encryptedStream, PGP_INVALID_PRIVATE_KEY_PATH, passphrase); if result is Error { check encryptedStream.close(); check inputStream.close(); @@ -121,11 +121,11 @@ isolated function testNegativeEncryptAndDecryptFileWithPgpInvalidPrivateKey() re @test:Config { serialExecution: true } -isolated function testNegativeEncryptAndDecryptFileWithPgpInvalidPassphrase() returns error? { +isolated function testNegativeEncryptAndDecryptStreamWithPgpInvalidPassphrase() returns error? { byte[] passphrase = "p7S5@T2MRFD9TQb".toBytes(); stream inputStream = check io:fileReadBlocksAsStream(SAMPLE_TEXT); stream encryptedStream = check encryptStreamPgp(inputStream, PGP_PUBLIC_KEY_PATH, symmetricKeyAlgorithm = AES_128, armor = false); - stream|Error result = check decryptStreamPgp(encryptedStream, PGP_PRIVATE_KEY_PATH, passphrase); + stream|Error result = decryptStreamPgp(encryptedStream, PGP_PRIVATE_KEY_PATH, passphrase); if result is Error { check encryptedStream.close(); check inputStream.close(); From 7c429ca3980fa09128ad974c8bbabe2abc8c2128 Mon Sep 17 00:00:00 2001 From: TharmiganK Date: Wed, 9 Oct 2024 14:37:19 +0530 Subject: [PATCH 33/42] Add license header --- ballerina/stream_iterators.bal | 16 ++++++++++++++++ .../stdlib/crypto/SequentialBufferedPipe.java | 17 +++++++++++++++++ 2 files changed, 33 insertions(+) diff --git a/ballerina/stream_iterators.bal b/ballerina/stream_iterators.bal index df04711..b936b6e 100644 --- a/ballerina/stream_iterators.bal +++ b/ballerina/stream_iterators.bal @@ -1,3 +1,19 @@ +// Copyright (c) 2024 WSO2 LLC. (https://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/jballerina.java; class DecryptedStreamIterator { diff --git a/native/src/main/java/io/ballerina/stdlib/crypto/SequentialBufferedPipe.java b/native/src/main/java/io/ballerina/stdlib/crypto/SequentialBufferedPipe.java index c07d967..4da7162 100644 --- a/native/src/main/java/io/ballerina/stdlib/crypto/SequentialBufferedPipe.java +++ b/native/src/main/java/io/ballerina/stdlib/crypto/SequentialBufferedPipe.java @@ -1,3 +1,20 @@ +/* + * Copyright (c) 2024 WSO2 LLC. (https://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; import java.io.InputStream; From 9d529a43bf263b7a4930720e8faf93d0cb4e8d23 Mon Sep 17 00:00:00 2001 From: TharmiganK Date: Wed, 9 Oct 2024 14:46:50 +0530 Subject: [PATCH 34/42] Add module prefix --- docs/spec/spec.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/spec/spec.md b/docs/spec/spec.md index 16da3c6..ccbdee1 100644 --- a/docs/spec/spec.md +++ b/docs/spec/spec.md @@ -537,7 +537,7 @@ key and return an encrypted stream ```ballerina stream inputStream = check io:fileReadBlocksAsStream("input.txt"); -stream|Error encryptedStream = crypto:encryptStreamPgp(inputStream, "public_key.asc"); +stream|io:Error encryptedStream = crypto:encryptStreamPgp(inputStream, "public_key.asc"); ``` ### 5.2. [Decryption](#52-decryption) @@ -632,7 +632,7 @@ PGP private key and passphrase and return a decrypted stream. ```ballerina stream inputStream = check io:fileReadBlocksAsStream("pgb_encrypted.txt"); -stream|Error decryptedStream = crypto:decryptStreamPgp(inputStream, "private_key.asc", passphrase); +stream|io:Error decryptedStream = crypto:decryptStreamPgp(inputStream, "private_key.asc", passphrase); ``` ## 6. [Sign and Verify](#6-sign-and-verify) From 6902dbec2ff4d6bd458f4d29014942447c341ad2 Mon Sep 17 00:00:00 2001 From: TharmiganK Date: Wed, 9 Oct 2024 14:51:35 +0530 Subject: [PATCH 35/42] Address sonar cloud issues --- .../stdlib/crypto/PgpDecryptionGenerator.java | 48 ++++++------------- 1 file changed, 15 insertions(+), 33 deletions(-) diff --git a/native/src/main/java/io/ballerina/stdlib/crypto/PgpDecryptionGenerator.java b/native/src/main/java/io/ballerina/stdlib/crypto/PgpDecryptionGenerator.java index ee72713..e622738 100644 --- a/native/src/main/java/io/ballerina/stdlib/crypto/PgpDecryptionGenerator.java +++ b/native/src/main/java/io/ballerina/stdlib/crypto/PgpDecryptionGenerator.java @@ -91,6 +91,11 @@ private Optional findSecretKey(long keyID) throws PGPException { private void decryptStream(InputStream encryptedIn, OutputStream clearOut) throws PGPException, IOException { + KeyEncryptedResult keyEncryptedResult = getKeyEncryptedResult(encryptedIn); + decrypt(clearOut, keyEncryptedResult.pgpPrivateKey(), keyEncryptedResult.publicKeyEncryptedData()); + } + + private KeyEncryptedResult getKeyEncryptedResult(InputStream encryptedIn) throws IOException, PGPException { // Remove armour and return the underlying binary encrypted stream encryptedIn = PGPUtil.getDecoderStream(encryptedIn); JcaPGPObjectFactory pgpObjectFactory = new JcaPGPObjectFactory(encryptedIn); @@ -116,36 +121,15 @@ private void decryptStream(InputStream encryptedIn, OutputStream clearOut) if (pgpPrivateKey.isEmpty()) { throw new PGPException("Could not Extract private key"); } - decrypt(clearOut, pgpPrivateKey.get(), publicKeyEncryptedData); + return new KeyEncryptedResult(pgpPrivateKey.get(), publicKeyEncryptedData); } - public void decryptStream(InputStream encryptedIn, BObject iteratorObj) throws PGPException, IOException { - // Remove armour and return the underlying binary encrypted stream - encryptedIn = PGPUtil.getDecoderStream(encryptedIn); - JcaPGPObjectFactory pgpObjectFactory = new JcaPGPObjectFactory(encryptedIn); - - Object obj = pgpObjectFactory.nextObject(); - // Verify the marker packet - PGPEncryptedDataList pgpEncryptedDataList = (obj instanceof PGPEncryptedDataList) - ? (PGPEncryptedDataList) obj : (PGPEncryptedDataList) pgpObjectFactory.nextObject(); - - Optional pgpPrivateKey = Optional.empty(); - PGPPublicKeyEncryptedData publicKeyEncryptedData = null; - - Iterator encryptedDataItr = pgpEncryptedDataList.getEncryptedDataObjects(); - while (pgpPrivateKey.isEmpty() && encryptedDataItr.hasNext()) { - publicKeyEncryptedData = (PGPPublicKeyEncryptedData) encryptedDataItr.next(); - pgpPrivateKey = findSecretKey(publicKeyEncryptedData.getKeyID()); - } - - if (Objects.isNull(publicKeyEncryptedData)) { - throw new PGPException("Could not generate PGPPublicKeyEncryptedData object"); - } + private record KeyEncryptedResult(PGPPrivateKey pgpPrivateKey, PGPPublicKeyEncryptedData publicKeyEncryptedData) { + } - if (pgpPrivateKey.isEmpty()) { - throw new PGPException("Could not Extract private key"); - } - decrypt(pgpPrivateKey.get(), publicKeyEncryptedData, iteratorObj); + public void decryptStream(InputStream encryptedIn, BObject iteratorObj) throws PGPException, IOException { + KeyEncryptedResult keyEncryptedResult = getKeyEncryptedResult(encryptedIn); + decrypt(keyEncryptedResult.pgpPrivateKey, keyEncryptedResult.publicKeyEncryptedData, iteratorObj); } // Decrypts the given byte array of encrypted data using PGP decryption. @@ -187,11 +171,10 @@ private static void decrypt(OutputStream clearOut, PGPPrivateKey pgpPrivateKey, } } // Perform the integrity check - if (publicKeyEncryptedData.isIntegrityProtected()) { - if (!publicKeyEncryptedData.verify()) { + if (publicKeyEncryptedData.isIntegrityProtected() && !publicKeyEncryptedData.verify()) { throw new PGPException("Message failed integrity check"); } - } + } private static void decrypt(PGPPrivateKey pgpPrivateKey, PGPPublicKeyEncryptedData publicKeyEncryptedData, @@ -209,11 +192,10 @@ private static void decrypt(PGPPrivateKey pgpPrivateKey, PGPPublicKeyEncryptedDa if (message instanceof PGPLiteralData pgpLiteralData) { // Perform the integrity check - if (publicKeyEncryptedData.isIntegrityProtected()) { - if (!publicKeyEncryptedData.verify()) { + if (publicKeyEncryptedData.isIntegrityProtected() && !publicKeyEncryptedData.verify()) { throw new PGPException("Message failed integrity check"); } - } + iteratorObj.addNativeData(TARGET_STREAM, pgpLiteralData.getDataStream()); iteratorObj.addNativeData(COMPRESSED_DATA_STREAM, compressedDataStream); iteratorObj.addNativeData(DATA_STREAM, decryptedCompressedIn); From 534c5a0d7303781ff2c7a725252335585bf2f0d2 Mon Sep 17 00:00:00 2001 From: TharmiganK Date: Wed, 9 Oct 2024 16:28:21 +0530 Subject: [PATCH 36/42] Fix integrity check --- .../io/ballerina/stdlib/crypto/Constants.java | 1 + .../stdlib/crypto/PgpDecryptionGenerator.java | 18 ++++------ .../stdlib/crypto/PgpEncryptionGenerator.java | 4 ++- .../stdlib/crypto/nativeimpl/StreamUtils.java | 35 +++++++++++++++++-- 4 files changed, 43 insertions(+), 15 deletions(-) diff --git a/native/src/main/java/io/ballerina/stdlib/crypto/Constants.java b/native/src/main/java/io/ballerina/stdlib/crypto/Constants.java index 279bff8..faf301a 100644 --- a/native/src/main/java/io/ballerina/stdlib/crypto/Constants.java +++ b/native/src/main/java/io/ballerina/stdlib/crypto/Constants.java @@ -134,4 +134,5 @@ private Constants() {} public static final String PIPED_OUTPUT_STREAM = "PIPED_OUTPUT_STREAM"; public static final String END_OF_INPUT_STREAM = "END_OF_INPUT_STREAM"; public static final String COMPRESSED_DATA_GENERATOR = "COMPRESSED_DATA_GENERATOR"; + public static final String KEY_ENCRYPTED_DATA = "KEY_ENCRYPTED_DATA"; } diff --git a/native/src/main/java/io/ballerina/stdlib/crypto/PgpDecryptionGenerator.java b/native/src/main/java/io/ballerina/stdlib/crypto/PgpDecryptionGenerator.java index e622738..810d047 100644 --- a/native/src/main/java/io/ballerina/stdlib/crypto/PgpDecryptionGenerator.java +++ b/native/src/main/java/io/ballerina/stdlib/crypto/PgpDecryptionGenerator.java @@ -51,6 +51,7 @@ import static io.ballerina.stdlib.crypto.Constants.COMPRESSED_DATA_STREAM; import static io.ballerina.stdlib.crypto.Constants.DATA_STREAM; +import static io.ballerina.stdlib.crypto.Constants.KEY_ENCRYPTED_DATA; import static io.ballerina.stdlib.crypto.Constants.TARGET_STREAM; /** @@ -102,8 +103,8 @@ private KeyEncryptedResult getKeyEncryptedResult(InputStream encryptedIn) throws Object obj = pgpObjectFactory.nextObject(); // Verify the marker packet - PGPEncryptedDataList pgpEncryptedDataList = (obj instanceof PGPEncryptedDataList) - ? (PGPEncryptedDataList) obj : (PGPEncryptedDataList) pgpObjectFactory.nextObject(); + PGPEncryptedDataList pgpEncryptedDataList = (obj instanceof PGPEncryptedDataList pgpEncryptedData) + ? pgpEncryptedData : (PGPEncryptedDataList) pgpObjectFactory.nextObject(); Optional pgpPrivateKey = Optional.empty(); PGPPublicKeyEncryptedData publicKeyEncryptedData = null; @@ -142,7 +143,7 @@ public Object decrypt(byte[] encryptedBytes) throws PGPException, IOException { } private static void decrypt(OutputStream clearOut, PGPPrivateKey pgpPrivateKey, - PGPPublicKeyEncryptedData publicKeyEncryptedData) throws IOException, PGPException { + PGPPublicKeyEncryptedData publicKeyEncryptedData) throws IOException, PGPException { PublicKeyDataDecryptorFactory decryptorFactory = new JcePublicKeyDataDecryptorFactoryBuilder() .setProvider(BouncyCastleProvider.PROVIDER_NAME).build(pgpPrivateKey); try (InputStream decryptedCompressedIn = publicKeyEncryptedData.getDataStream(decryptorFactory)) { @@ -172,9 +173,8 @@ private static void decrypt(OutputStream clearOut, PGPPrivateKey pgpPrivateKey, } // Perform the integrity check if (publicKeyEncryptedData.isIntegrityProtected() && !publicKeyEncryptedData.verify()) { - throw new PGPException("Message failed integrity check"); - } - + throw new PGPException("Message failed integrity check"); + } } private static void decrypt(PGPPrivateKey pgpPrivateKey, PGPPublicKeyEncryptedData publicKeyEncryptedData, @@ -191,11 +191,7 @@ private static void decrypt(PGPPrivateKey pgpPrivateKey, PGPPublicKeyEncryptedDa Object message = pgpCompObjFac.nextObject(); if (message instanceof PGPLiteralData pgpLiteralData) { - // Perform the integrity check - if (publicKeyEncryptedData.isIntegrityProtected() && !publicKeyEncryptedData.verify()) { - throw new PGPException("Message failed integrity check"); - } - + iteratorObj.addNativeData(KEY_ENCRYPTED_DATA, publicKeyEncryptedData); iteratorObj.addNativeData(TARGET_STREAM, pgpLiteralData.getDataStream()); iteratorObj.addNativeData(COMPRESSED_DATA_STREAM, compressedDataStream); iteratorObj.addNativeData(DATA_STREAM, decryptedCompressedIn); diff --git a/native/src/main/java/io/ballerina/stdlib/crypto/PgpEncryptionGenerator.java b/native/src/main/java/io/ballerina/stdlib/crypto/PgpEncryptionGenerator.java index f2145c7..e9ccff5 100644 --- a/native/src/main/java/io/ballerina/stdlib/crypto/PgpEncryptionGenerator.java +++ b/native/src/main/java/io/ballerina/stdlib/crypto/PgpEncryptionGenerator.java @@ -50,6 +50,7 @@ import java.util.Optional; import static io.ballerina.stdlib.crypto.Constants.COMPRESSED_DATA_GENERATOR; +import static io.ballerina.stdlib.crypto.Constants.COMPRESSED_DATA_STREAM; import static io.ballerina.stdlib.crypto.Constants.DATA_STREAM; import static io.ballerina.stdlib.crypto.Constants.ENCRYPTED_OUTPUT_STREAM; import static io.ballerina.stdlib.crypto.Constants.PIPED_INPUT_STREAM; @@ -77,7 +78,7 @@ public class PgpEncryptionGenerator { // The constructor of the PGP encryption generator. public PgpEncryptionGenerator(int compressionAlgorithm, int symmetricKeyAlgorithm, boolean armor, - boolean withIntegrityCheck) { + boolean withIntegrityCheck) { this.compressionAlgorithm = compressionAlgorithm; this.symmetricKeyAlgorithm = symmetricKeyAlgorithm; this.armor = armor; @@ -134,6 +135,7 @@ public void encryptStream(InputStream publicKeyIn, BObject iteratorObj) OutputStream cipherOutStream = pgpEncryptedDataGenerator.open(encryptOut, new byte[BUFFER_SIZE]); OutputStream compressedOutStream = compressedDataGenerator.open(cipherOutStream); iteratorObj.addNativeData(DATA_STREAM, cipherOutStream); + iteratorObj.addNativeData(COMPRESSED_DATA_STREAM, compressedOutStream); iteratorObj.addNativeData(COMPRESSED_DATA_GENERATOR, compressedDataGenerator); copyAsLiteralData(compressedOutStream, iteratorObj); } diff --git a/native/src/main/java/io/ballerina/stdlib/crypto/nativeimpl/StreamUtils.java b/native/src/main/java/io/ballerina/stdlib/crypto/nativeimpl/StreamUtils.java index ff9c0cb..a2cbf3e 100644 --- a/native/src/main/java/io/ballerina/stdlib/crypto/nativeimpl/StreamUtils.java +++ b/native/src/main/java/io/ballerina/stdlib/crypto/nativeimpl/StreamUtils.java @@ -23,6 +23,8 @@ import io.ballerina.runtime.api.values.BObject; import io.ballerina.stdlib.crypto.CryptoUtils; import org.bouncycastle.openpgp.PGPCompressedDataGenerator; +import org.bouncycastle.openpgp.PGPException; +import org.bouncycastle.openpgp.PGPPublicKeyEncryptedData; import java.io.Closeable; import java.io.IOException; @@ -34,6 +36,7 @@ import static io.ballerina.stdlib.crypto.Constants.ENCRYPTED_OUTPUT_STREAM; import static io.ballerina.stdlib.crypto.Constants.END_OF_INPUT_STREAM; import static io.ballerina.stdlib.crypto.Constants.INPUT_STREAM_TO_ENCRYPT; +import static io.ballerina.stdlib.crypto.Constants.KEY_ENCRYPTED_DATA; import static io.ballerina.stdlib.crypto.Constants.PIPED_INPUT_STREAM; import static io.ballerina.stdlib.crypto.Constants.COMPRESSED_DATA_STREAM; import static io.ballerina.stdlib.crypto.Constants.DATA_STREAM; @@ -55,6 +58,11 @@ public final class StreamUtils { public static final String ERROR_OCCURRED_WHILE_READING_THE_STREAM = "Error occurred while reading from the " + "stream: %s"; public static final String NATIVE_DATA_NOT_AVAILABLE_ERROR = "%s is not available"; + public static final String MESSAGE_FAILED_INTEGRITY_CHECK = "Message failed integrity check"; + public static final String ERROR_OCCURRED_WHILE_VERIFYING_THE_INTEGRITY = "Error occurred while verifying the" + + " integrity: %s"; + public static final String ERROR_OCCURRED_WHILE_READING_FROM_THE_STREAM = "Error occurred while reading from " + + "the stream: %s"; private StreamUtils() { } @@ -68,6 +76,8 @@ public static Object readDecryptedStream(BObject iterator) { byte[] buffer = new byte[BUFFER_SIZE]; int in = inputStream.read(buffer); if (in == -1) { + closeNativeStream(iterator, TARGET_STREAM); + performIntegrityCheck(iterator); return null; } if (in < buffer.length) { @@ -77,7 +87,23 @@ public static Object readDecryptedStream(BObject iterator) { } return ValueCreator.createArrayValue(buffer); } catch (IOException e) { - return CryptoUtils.createError("Error occurred while reading from the stream: " + e.getMessage()); + return CryptoUtils.createError(String.format(ERROR_OCCURRED_WHILE_READING_FROM_THE_STREAM, + e.getMessage())); + } + } + + private static void performIntegrityCheck(BObject iterator) throws IOException { + Object publicKeyEncryptedDataObj = iterator.getNativeData(KEY_ENCRYPTED_DATA); + if (Objects.isNull(publicKeyEncryptedDataObj) || !(publicKeyEncryptedDataObj instanceof + PGPPublicKeyEncryptedData publicKeyEncryptedData)) { + throw CryptoUtils.createError(STREAM_NOT_AVAILABLE); + } + try { + if (publicKeyEncryptedData.isIntegrityProtected() && !publicKeyEncryptedData.verify()) { + throw CryptoUtils.createError(MESSAGE_FAILED_INTEGRITY_CHECK); + } + } catch (PGPException e) { + throw CryptoUtils.createError(String.format(ERROR_OCCURRED_WHILE_VERIFYING_THE_INTEGRITY, e.getMessage())); } } @@ -88,7 +114,7 @@ public static Object readEncryptedStream(BObject iterator) { if (Boolean.FALSE.equals(nativeData.endOfStream())) { writeToOutStream(iterator, nativeData.inputStream(), nativeData.outputStream()); } - return readFromPipedStream(nativeData.pipedInStream()); + return readFromPipedStream(iterator, nativeData.pipedInStream()); } catch (IOException e) { return CryptoUtils.createError(String.format(ERROR_OCCURRED_WHILE_READING_THE_STREAM, e.getMessage())); } catch (BError e) { @@ -123,10 +149,11 @@ private record NativeData(InputStream inputStream, OutputStream outputStream, In Boolean endOfStream) { } - private static BArray readFromPipedStream(InputStream pipedInStream) throws IOException { + private static BArray readFromPipedStream(BObject iterator, InputStream pipedInStream) throws IOException { byte[] pipelinedBuffer = new byte[BUFFER_SIZE]; int pipelinedIn = pipedInStream.read(pipelinedBuffer); if (pipelinedIn == -1) { + closeNativeStream(iterator, PIPED_INPUT_STREAM); return null; } if (pipelinedIn < pipelinedBuffer.length) { @@ -158,6 +185,7 @@ public static void closeDecryptedStream(BObject iterator) throws BError { public static void closeEncryptedStream(BObject iterator) throws BError { closeNativeStream(iterator, TARGET_STREAM); + closeNativeStream(iterator, COMPRESSED_DATA_STREAM); closeDataGenerator(iterator); closeNativeStream(iterator, DATA_STREAM); closeNativeStream(iterator, ENCRYPTED_OUTPUT_STREAM); @@ -168,6 +196,7 @@ public static void closeEncryptedStream(BObject iterator) throws BError { public static void closeEncryptedSourceStreams(BObject iterator) throws BError { closeNativeStream(iterator, INPUT_STREAM_TO_ENCRYPT); closeNativeStream(iterator, TARGET_STREAM); + closeNativeStream(iterator, COMPRESSED_DATA_STREAM); closeDataGenerator(iterator); closeNativeStream(iterator, DATA_STREAM); closeNativeStream(iterator, ENCRYPTED_OUTPUT_STREAM); From a44340956fb6cd02a5032b31e80f1f4b5a808dee Mon Sep 17 00:00:00 2001 From: Krishnananthalingam Tharmigan <63336800+TharmiganK@users.noreply.github.com> Date: Thu, 10 Oct 2024 10:24:17 +0530 Subject: [PATCH 37/42] Apply suggestions from code review --- docs/spec/spec.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/spec/spec.md b/docs/spec/spec.md index ccbdee1..7486418 100644 --- a/docs/spec/spec.md +++ b/docs/spec/spec.md @@ -537,7 +537,7 @@ key and return an encrypted stream ```ballerina stream inputStream = check io:fileReadBlocksAsStream("input.txt"); -stream|io:Error encryptedStream = crypto:encryptStreamPgp(inputStream, "public_key.asc"); +stream|crypto:Error encryptedStream = crypto:encryptStreamPgp(inputStream, "public_key.asc"); ``` ### 5.2. [Decryption](#52-decryption) @@ -632,7 +632,7 @@ PGP private key and passphrase and return a decrypted stream. ```ballerina stream inputStream = check io:fileReadBlocksAsStream("pgb_encrypted.txt"); -stream|io:Error decryptedStream = crypto:decryptStreamPgp(inputStream, "private_key.asc", passphrase); +stream|crypto:Error decryptedStream = crypto:decryptStreamPgp(inputStream, "private_key.asc", passphrase); ``` ## 6. [Sign and Verify](#6-sign-and-verify) From c79289034ea447eea22f65b3a02a7c6c43f39ae3 Mon Sep 17 00:00:00 2001 From: TharmiganK Date: Thu, 10 Oct 2024 11:22:32 +0530 Subject: [PATCH 38/42] Remove unused constants --- .../java/io/ballerina/stdlib/crypto/BallerinaInputStream.java | 4 ---- 1 file changed, 4 deletions(-) diff --git a/native/src/main/java/io/ballerina/stdlib/crypto/BallerinaInputStream.java b/native/src/main/java/io/ballerina/stdlib/crypto/BallerinaInputStream.java index 909367e..08fb2b6 100644 --- a/native/src/main/java/io/ballerina/stdlib/crypto/BallerinaInputStream.java +++ b/native/src/main/java/io/ballerina/stdlib/crypto/BallerinaInputStream.java @@ -39,12 +39,8 @@ public class BallerinaInputStream extends InputStream { public static final String STREAM_VALUE = "value"; public static final String BAL_STREAM_NEXT = "next"; - public static final String ERROR_OCCURRED_WHILE_CLOSING_THE_STREAM = "Error occurred while closing the stream"; public static final String ERROR_OCCURRED_WHILE_READING_THE_STREAM = "Error occurred while reading the next " + "element from the stream"; - public static final String INTERRUPTED_ERROR_WHILE_READING_THE_STREAM = ERROR_OCCURRED_WHILE_READING_THE_STREAM + - ": interrupted exception"; - public static final String ERR_MSG_FORMAT = "%s: %s"; public static final String UNEXPECTED_TYPE_ERROR = ERROR_OCCURRED_WHILE_READING_THE_STREAM + ": unexpected value type"; From 0f85fef6c0ba4b60cdd01a949bdb23611ad0a4ae Mon Sep 17 00:00:00 2001 From: TharmiganK Date: Fri, 11 Oct 2024 10:40:17 +0530 Subject: [PATCH 39/42] Rename PGP stream APIs --- ballerina/encrypt_decrypt.bal | 10 +++++----- ballerina/tests/encrypt_decrypt_pgp_test.bal | 16 ++++++++-------- docs/spec/spec.md | 4 ++-- .../stdlib/crypto/nativeimpl/Decrypt.java | 2 +- .../stdlib/crypto/nativeimpl/Encrypt.java | 2 +- 5 files changed, 17 insertions(+), 17 deletions(-) diff --git a/ballerina/encrypt_decrypt.bal b/ballerina/encrypt_decrypt.bal index ba52052..543d34f 100644 --- a/ballerina/encrypt_decrypt.bal +++ b/ballerina/encrypt_decrypt.bal @@ -263,13 +263,13 @@ public isolated function encryptPgp(byte[] plainText, string publicKeyPath, *Opt # Returns the PGP-encrypted stream of the content given in the input stream. # ```ballerina # stream inputStream = check io:fileReadBlocksAsStream("input.txt"); -# stream|Error encryptedStream = crypto:encryptStreamPgp(inputStream, "public_key.asc"); +# stream|crypto:Error encryptedStream = crypto:encryptStreamAsPgp(inputStream, "public_key.asc"); # ``` # # + inputStream - The content to be encrypted as a stream # + privateKeyPath - Path to the private key # + return - Encrypted stream or else a `crypto:Error` if the key is invalid -public isolated function encryptStreamPgp(stream inputStream, string publicKeyPath, +public isolated function encryptStreamAsPgp(stream inputStream, string publicKeyPath, *Options options) returns stream|Error = @java:Method { 'class: "io.ballerina.stdlib.crypto.nativeimpl.Encrypt" } external; @@ -284,7 +284,7 @@ public isolated function encryptStreamPgp(stream inputStream, st # ``` # # + cipherText - The encrypted content to be decrypted -# + privateKeyPath - Path to the private key +# + privateKey - # + passphrase - passphrase of the private key # + return - Decrypted data or else a `crypto:Error` if the key or passphrase is invalid public isolated function decryptPgp(byte[] cipherText, string privateKeyPath, byte[] passphrase) @@ -297,14 +297,14 @@ public isolated function decryptPgp(byte[] cipherText, string privateKeyPath, by # ```ballerina # byte[] passphrase = check io:fileReadBytes("pass_phrase.txt"); # stream inputStream = check io:fileReadBlocksAsStream("pgb_encrypted.txt"); -# stream|Error decryptedStream = crypto:decryptStreamPgp(inputStream, "private_key.asc", passphrase); +# stream|crypto:Error decryptedStream = crypto:decryptStreamFromPgp(inputStream, "private_key.asc", passphrase); # ``` # # + inputStream - The encrypted content as a stream # + privateKeyPath - Path to the private key # + passphrase - passphrase of the private key # + return - Decrypted stream or else a `crypto:Error` if the key or passphrase is invalid -public isolated function decryptStreamPgp(stream inputStream, string privateKeyPath, +public isolated function decryptStreamFromPgp(stream inputStream, string privateKeyPath, byte[] passphrase) returns stream|Error = @java:Method { 'class: "io.ballerina.stdlib.crypto.nativeimpl.Decrypt" } external; diff --git a/ballerina/tests/encrypt_decrypt_pgp_test.bal b/ballerina/tests/encrypt_decrypt_pgp_test.bal index c9188d7..0b84d0e 100644 --- a/ballerina/tests/encrypt_decrypt_pgp_test.bal +++ b/ballerina/tests/encrypt_decrypt_pgp_test.bal @@ -68,8 +68,8 @@ isolated function testNegativeEncryptAndDecryptWithPgpInvalidPassphrase() return isolated function testEncryptAndDecryptStreamWithPgp() returns error? { byte[] passphrase = "qCr3bv@5mj5n4eY".toBytes(); stream inputStream = check io:fileReadBlocksAsStream(SAMPLE_TEXT); - stream encryptedStream = check encryptStreamPgp(inputStream, PGP_PUBLIC_KEY_PATH); - stream decryptedStream = check decryptStreamPgp(encryptedStream, PGP_PRIVATE_KEY_PATH, passphrase); + stream encryptedStream = check encryptStreamAsPgp(inputStream, PGP_PUBLIC_KEY_PATH); + stream decryptedStream = check decryptStreamFromPgp(encryptedStream, PGP_PRIVATE_KEY_PATH, passphrase); byte[] expected = check io:fileReadBytes(SAMPLE_TEXT); byte[] actual = []; @@ -86,8 +86,8 @@ isolated function testEncryptAndDecryptStreamWithPgp() returns error? { isolated function testEncryptAndDecryptStreamWithPgpWithOptions() returns error? { byte[] passphrase = "qCr3bv@5mj5n4eY".toBytes(); stream inputStream = check io:fileReadBlocksAsStream(SAMPLE_TEXT); - stream encryptedStream = check encryptStreamPgp(inputStream, PGP_PUBLIC_KEY_PATH, symmetricKeyAlgorithm = AES_128, armor = false); - stream decryptedStream = check decryptStreamPgp(encryptedStream, PGP_PRIVATE_KEY_PATH, passphrase); + stream encryptedStream = check encryptStreamAsPgp(inputStream, PGP_PUBLIC_KEY_PATH, symmetricKeyAlgorithm = AES_128, armor = false); + stream decryptedStream = check decryptStreamFromPgp(encryptedStream, PGP_PRIVATE_KEY_PATH, passphrase); byte[] expected = check io:fileReadBytes(SAMPLE_TEXT); byte[] actual = []; @@ -104,8 +104,8 @@ isolated function testEncryptAndDecryptStreamWithPgpWithOptions() returns error? isolated function testNegativeEncryptAndDecryptStreamWithPgpInvalidPrivateKey() returns error? { byte[] passphrase = "p7S5@T2MRFD9TQb".toBytes(); stream inputStream = check io:fileReadBlocksAsStream(SAMPLE_TEXT); - stream encryptedStream = check encryptStreamPgp(inputStream, PGP_PUBLIC_KEY_PATH, symmetricKeyAlgorithm = AES_128, armor = false); - stream|Error result = decryptStreamPgp(encryptedStream, PGP_INVALID_PRIVATE_KEY_PATH, passphrase); + stream encryptedStream = check encryptStreamAsPgp(inputStream, PGP_PUBLIC_KEY_PATH, symmetricKeyAlgorithm = AES_128, armor = false); + stream|Error result = decryptStreamFromPgp(encryptedStream, PGP_INVALID_PRIVATE_KEY_PATH, passphrase); if result is Error { check encryptedStream.close(); check inputStream.close(); @@ -124,8 +124,8 @@ isolated function testNegativeEncryptAndDecryptStreamWithPgpInvalidPrivateKey() isolated function testNegativeEncryptAndDecryptStreamWithPgpInvalidPassphrase() returns error? { byte[] passphrase = "p7S5@T2MRFD9TQb".toBytes(); stream inputStream = check io:fileReadBlocksAsStream(SAMPLE_TEXT); - stream encryptedStream = check encryptStreamPgp(inputStream, PGP_PUBLIC_KEY_PATH, symmetricKeyAlgorithm = AES_128, armor = false); - stream|Error result = decryptStreamPgp(encryptedStream, PGP_PRIVATE_KEY_PATH, passphrase); + stream encryptedStream = check encryptStreamAsPgp(inputStream, PGP_PUBLIC_KEY_PATH, symmetricKeyAlgorithm = AES_128, armor = false); + stream|Error result = decryptStreamFromPgp(encryptedStream, PGP_PRIVATE_KEY_PATH, passphrase); if result is Error { check encryptedStream.close(); check inputStream.close(); diff --git a/docs/spec/spec.md b/docs/spec/spec.md index 7486418..40d5151 100644 --- a/docs/spec/spec.md +++ b/docs/spec/spec.md @@ -537,7 +537,7 @@ key and return an encrypted stream ```ballerina stream inputStream = check io:fileReadBlocksAsStream("input.txt"); -stream|crypto:Error encryptedStream = crypto:encryptStreamPgp(inputStream, "public_key.asc"); +stream|crypto:Error encryptedStream = crypto:encryptStreamAsPgp(inputStream, "public_key.asc"); ``` ### 5.2. [Decryption](#52-decryption) @@ -632,7 +632,7 @@ PGP private key and passphrase and return a decrypted stream. ```ballerina stream inputStream = check io:fileReadBlocksAsStream("pgb_encrypted.txt"); -stream|crypto:Error decryptedStream = crypto:decryptStreamPgp(inputStream, "private_key.asc", passphrase); +stream|crypto:Error decryptedStream = crypto:decryptStreamFromPgp(inputStream, "private_key.asc", passphrase); ``` ## 6. [Sign and Verify](#6-sign-and-verify) diff --git a/native/src/main/java/io/ballerina/stdlib/crypto/nativeimpl/Decrypt.java b/native/src/main/java/io/ballerina/stdlib/crypto/nativeimpl/Decrypt.java index 428cda3..ff5d81e 100644 --- a/native/src/main/java/io/ballerina/stdlib/crypto/nativeimpl/Decrypt.java +++ b/native/src/main/java/io/ballerina/stdlib/crypto/nativeimpl/Decrypt.java @@ -113,7 +113,7 @@ public static Object decryptPgp(BArray cipherTextValue, BString privateKeyPath, } } - public static Object decryptStreamPgp(Environment environment, BStream inputBalStream, BString privateKeyPath, + public static Object decryptStreamFromPgp(Environment environment, BStream inputBalStream, BString privateKeyPath, BArray passphrase) { byte[] passphraseInBytes = passphrase.getBytes(); byte[] privateKey; diff --git a/native/src/main/java/io/ballerina/stdlib/crypto/nativeimpl/Encrypt.java b/native/src/main/java/io/ballerina/stdlib/crypto/nativeimpl/Encrypt.java index aa2870a..50aea03 100644 --- a/native/src/main/java/io/ballerina/stdlib/crypto/nativeimpl/Encrypt.java +++ b/native/src/main/java/io/ballerina/stdlib/crypto/nativeimpl/Encrypt.java @@ -130,7 +130,7 @@ public static Object encryptPgp(BArray plainTextValue, BString publicKeyPath, BM } } - public static Object encryptStreamPgp(Environment environment, BStream inputBalStream, BString publicKeyPath, + public static Object encryptStreamAsPgp(Environment environment, BStream inputBalStream, BString publicKeyPath, BMap options) { byte[] publicKey; try { From 03c6d8f0a257dfb57becb111c7ebbffedf356cd5 Mon Sep 17 00:00:00 2001 From: Krishnananthalingam Tharmigan <63336800+TharmiganK@users.noreply.github.com> Date: Fri, 11 Oct 2024 14:10:59 +0530 Subject: [PATCH 40/42] Apply suggestions from code review Co-authored-by: Bhashinee --- ballerina/encrypt_decrypt.bal | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ballerina/encrypt_decrypt.bal b/ballerina/encrypt_decrypt.bal index 375d9b4..94cb921 100644 --- a/ballerina/encrypt_decrypt.bal +++ b/ballerina/encrypt_decrypt.bal @@ -267,7 +267,7 @@ public isolated function encryptPgp(byte[] plainText, string publicKey, *Options # ``` # # + inputStream - The content to be encrypted as a stream -# + privateKey - Path to the private key +# + publicKey - Path to the public key # + return - Encrypted stream or else a `crypto:Error` if the key is invalid public isolated function encryptStreamAsPgp(stream inputStream, string publicKey, *Options options) returns stream|Error = @java:Method { From f019b5436a4ea194661ae70686be614298959ee7 Mon Sep 17 00:00:00 2001 From: TharmiganK Date: Mon, 14 Oct 2024 08:56:10 +0530 Subject: [PATCH 41/42] Add missing doc for options --- ballerina/encrypt_decrypt.bal | 1 + 1 file changed, 1 insertion(+) diff --git a/ballerina/encrypt_decrypt.bal b/ballerina/encrypt_decrypt.bal index 94cb921..b4ced6a 100644 --- a/ballerina/encrypt_decrypt.bal +++ b/ballerina/encrypt_decrypt.bal @@ -268,6 +268,7 @@ public isolated function encryptPgp(byte[] plainText, string publicKey, *Options # # + inputStream - The content to be encrypted as a stream # + publicKey - Path to the public key +# + options - PGP encryption options # + return - Encrypted stream or else a `crypto:Error` if the key is invalid public isolated function encryptStreamAsPgp(stream inputStream, string publicKey, *Options options) returns stream|Error = @java:Method { From db345a05ffd0ad82d6bfc17324c40cf092849f6e Mon Sep 17 00:00:00 2001 From: TharmiganK Date: Mon, 14 Oct 2024 08:56:30 +0530 Subject: [PATCH 42/42] Replace experimental graalvm options --- .../io.ballerina.stdlib/crypto-native/native-image.properties | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/native/src/main/resources/META-INF/native-image/io.ballerina.stdlib/crypto-native/native-image.properties b/native/src/main/resources/META-INF/native-image/io.ballerina.stdlib/crypto-native/native-image.properties index 806d73d..45f2f15 100644 --- a/native/src/main/resources/META-INF/native-image/io.ballerina.stdlib/crypto-native/native-image.properties +++ b/native/src/main/resources/META-INF/native-image/io.ballerina.stdlib/crypto-native/native-image.properties @@ -14,5 +14,6 @@ # specific language governing permissions and limitations # under the License. -Args = -H:ClassInitialization=org.bouncycastle.jcajce.provider.drbg.DRBG\$Default:rerun,org.bouncycastle.jcajce.provider.drbg.DRBG\$NonceAndIV:rerun \ +Args = --initialize-at-run-time=org.bouncycastle.jcajce.provider.drbg.DRBG\$Default \ + --initialize-at-run-time=org.bouncycastle.jcajce.provider.drbg.DRBG\$NonceAndIV \ --features=io.ballerina.stdlib.crypto.svm.BouncyCastleFeature