Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Update 11 Feature] Add support for PGP encryption/decryption with streams #567

Open
wants to merge 45 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 42 commits
Commits
Show all changes
45 commits
Select commit Hold shift + click to select a range
6a7cb9b
Migrate to Java 21
ravinperera00 Sep 30, 2024
5ecc9ab
Merge pull request #566 from ravinperera00/java21
warunalakshitha Sep 30, 2024
0c230e2
[Automated] Update the native jar versions
TharmiganK Oct 2, 2024
85c3173
[Automated] Update the native jar versions
TharmiganK Oct 2, 2024
3ac65fd
Add PGP encrypt/decrypt functions with files
TharmiganK Oct 2, 2024
0fbae6b
Apply suggestions from code review
TharmiganK Oct 2, 2024
ae7af8c
[Automated] Update the native jar versions
TharmiganK Oct 2, 2024
3deeba8
Add test cases
TharmiganK Oct 2, 2024
c930edd
Update change log
TharmiganK Oct 2, 2024
04ba4ca
Update spec
TharmiganK Oct 2, 2024
3654143
Update to next minor version
TharmiganK Oct 2, 2024
1fa0552
[Automated] Update the native jar versions
TharmiganK Oct 2, 2024
95f34c1
[Automated] Update the native jar versions
TharmiganK Oct 2, 2024
957f623
Add IO test dependency
TharmiganK Oct 2, 2024
5ba88d7
Add suggestions from review
TharmiganK Oct 2, 2024
e207f95
Change error message
TharmiganK Oct 2, 2024
18ff4cc
Update time version
TharmiganK Oct 2, 2024
95c6beb
Add stream pgp decrypt support
TharmiganK Oct 7, 2024
7c584bb
Fix casting issue
TharmiganK Oct 7, 2024
d09e235
[Automated] Update the native jar versions
TharmiganK Oct 8, 2024
1126113
[Automated] Update the native jar versions
TharmiganK Oct 8, 2024
ea708af
Add stream pgp encrypt support
TharmiganK Oct 9, 2024
7094e49
Replace PipedStreams with a custom implementation
TharmiganK Oct 9, 2024
b635c45
Remove PGP file APIs
TharmiganK Oct 9, 2024
9cdd933
Fix test cases
TharmiganK Oct 9, 2024
29333dc
Update change log
TharmiganK Oct 9, 2024
f71ebb7
Update spec
TharmiganK Oct 9, 2024
9169721
Update ballerina invoke call
TharmiganK Oct 9, 2024
be0f1cb
Merge remote-tracking branch 'origin/java21' into pgp-files
TharmiganK Oct 9, 2024
1ba983d
[Automated] Update the native jar versions
TharmiganK Oct 9, 2024
132cd31
[Automated] Update the native jar versions
TharmiganK Oct 9, 2024
a51aa97
[Automated] Update the native jar versions
TharmiganK Oct 9, 2024
7139227
Update dependency versions supported with Java 21
TharmiganK Oct 9, 2024
43b3903
Rename test functions
TharmiganK Oct 9, 2024
7c429ca
Add license header
TharmiganK Oct 9, 2024
9d529a4
Add module prefix
TharmiganK Oct 9, 2024
6902dbe
Address sonar cloud issues
TharmiganK Oct 9, 2024
534c5a0
Fix integrity check
TharmiganK Oct 9, 2024
a443409
Apply suggestions from code review
TharmiganK Oct 10, 2024
c792890
Remove unused constants
TharmiganK Oct 10, 2024
0f85fef
Rename PGP stream APIs
TharmiganK Oct 11, 2024
c9aca36
Merge remote-tracking branch 'origin/master' into pgp-files
TharmiganK Oct 11, 2024
03c6d8f
Apply suggestions from code review
TharmiganK Oct 11, 2024
f019b54
Add missing doc for options
TharmiganK Oct 14, 2024
db345a0
Replace experimental graalvm options
TharmiganK Oct 14, 2024
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/workflows/build-timestamped-master.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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
2 changes: 1 addition & 1 deletion .github/workflows/build-with-bal-test-graalvm.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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 }}
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/central-publish.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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 }}
2 changes: 1 addition & 1 deletion .github/workflows/publish-release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/pull-request.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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
2 changes: 1 addition & 1 deletion .github/workflows/trivy-scan.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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/)

Expand Down
18 changes: 9 additions & 9 deletions ballerina/Ballerina.toml
Original file line number Diff line number Diff line change
@@ -1,42 +1,42 @@
[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"
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.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]]
[[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"
Expand Down
29 changes: 26 additions & 3 deletions ballerina/Dependencies.toml
Original file line number Diff line number Diff line change
Expand Up @@ -5,13 +5,14 @@

[ballerina]
dependencies-toml-version = "2"
distribution-version = "2201.9.0"
distribution-version = "2201.10.0-20240926-231800-8a5a4343"

[[package]]
org = "ballerina"
name = "crypto"
version = "2.7.3"
version = "2.8.0"
dependencies = [
{org = "ballerina", name = "io"},
{org = "ballerina", name = "jballerina.java"},
{org = "ballerina", name = "lang.array"},
{org = "ballerina", name = "test"},
Expand All @@ -21,6 +22,19 @@ 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"
Expand Down Expand Up @@ -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"
Expand All @@ -84,7 +107,7 @@ modules = [
[[package]]
org = "ballerina"
name = "time"
version = "2.4.0"
version = "2.5.1"
dependencies = [
{org = "ballerina", name = "jballerina.java"}
]
Expand Down
36 changes: 33 additions & 3 deletions ballerina/encrypt_decrypt.bal
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -260,11 +260,25 @@ public isolated function encryptPgp(byte[] plainText, string publicKey, *Options
'class: "io.ballerina.stdlib.crypto.nativeimpl.Encrypt"
} external;

# Returns the PGP-encrypted stream of the content given in the input stream.
# ```ballerina
# stream<byte[], error?> inputStream = check io:fileReadBlocksAsStream("input.txt");
# stream<byte[], crypto:Error?>|crypto:Error encryptedStream = crypto:encryptStreamAsPgp(inputStream, "public_key.asc");
# ```
#
# + inputStream - The content to be encrypted as a stream
# + privateKey - Path to the private key
TharmiganK marked this conversation as resolved.
Show resolved Hide resolved
# + return - Encrypted stream or else a `crypto:Error` if the key is invalid
public isolated function encryptStreamAsPgp(stream<byte[], error?> inputStream, string publicKey,
*Options options) returns stream<byte[], Error?>|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();
# 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);
# ```
Expand All @@ -278,3 +292,19 @@ public isolated function decryptPgp(byte[] cipherText, string privateKey, byte[]
name: "decryptPgp",
'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<byte[], error?> inputStream = check io:fileReadBlocksAsStream("pgb_encrypted.txt");
# stream<byte[], crypto:Error?>|crypto:Error decryptedStream = crypto:decryptStreamFromPgp(inputStream, "private_key.asc", passphrase);
# ```
#
# + inputStream - The encrypted content as a stream
# + privateKey - 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 decryptStreamFromPgp(stream<byte[], error?> inputStream, string privateKey,
byte[] passphrase) returns stream<byte[], Error?>|Error = @java:Method {
'class: "io.ballerina.stdlib.crypto.nativeimpl.Decrypt"
} external;
4 changes: 2 additions & 2 deletions ballerina/private_public_key.bal
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down Expand Up @@ -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"
Expand Down
81 changes: 81 additions & 0 deletions ballerina/stream_iterators.bal
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
// 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 {
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();

Check warning on line 33 in ballerina/stream_iterators.bal

View check run for this annotation

Codecov / codecov/patch

ballerina/stream_iterators.bal#L33

Added line #L33 was not covered by tests
if closeResult is () {
self.isClosed = true;

Check warning on line 35 in ballerina/stream_iterators.bal

View check run for this annotation

Codecov / codecov/patch

ballerina/stream_iterators.bal#L35

Added line #L35 was not covered by tests
}
return closeResult;

Check warning on line 37 in ballerina/stream_iterators.bal

View check run for this annotation

Codecov / codecov/patch

ballerina/stream_iterators.bal#L37

Added line #L37 was not covered by tests
}
return;
}

Check warning on line 40 in ballerina/stream_iterators.bal

View check run for this annotation

Codecov / codecov/patch

ballerina/stream_iterators.bal#L39-L40

Added lines #L39 - L40 were not covered by tests

isolated function readDecryptedStream() returns byte[]|Error? = @java:Method {
'class: "io.ballerina.stdlib.crypto.nativeimpl.StreamUtils"
} external;

isolated function closeDecryptedStream() returns Error? = @java:Method {

Check warning on line 46 in ballerina/stream_iterators.bal

View check run for this annotation

Codecov / codecov/patch

ballerina/stream_iterators.bal#L46

Added line #L46 was not covered by tests
'class: "io.ballerina.stdlib.crypto.nativeimpl.StreamUtils"
} external;

Check warning on line 48 in ballerina/stream_iterators.bal

View check run for this annotation

Codecov / codecov/patch

ballerina/stream_iterators.bal#L48

Added line #L48 was not covered by tests
}

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;

Check warning on line 59 in ballerina/stream_iterators.bal

View check run for this annotation

Codecov / codecov/patch

ballerina/stream_iterators.bal#L59

Added line #L59 was not covered by tests
}
}

public isolated function close() returns Error? {
if !self.isClosed {
var closeResult = self.closeEncryptedStream();
if closeResult is () {
self.isClosed = true;
}
return closeResult;
}
return;

Check warning on line 71 in ballerina/stream_iterators.bal

View check run for this annotation

Codecov / codecov/patch

ballerina/stream_iterators.bal#L71

Added line #L71 was not covered by tests
}

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;
}
Loading
Loading