From 9110389e0bb5281b52057bdaf45ca81fcb231d9d Mon Sep 17 00:00:00 2001 From: Dinuka Amarasinghe Date: Thu, 9 May 2024 18:07:38 +0530 Subject: [PATCH] Add pgp encryption and decryption --- ballerina/build.gradle | 3 + ballerina/encrypt_decrypt.bal | 38 +++++ ballerina/pgp_utils.bal | 46 ++++++ ballerina/tests/encrypt_decrypt_pgp_test.bal | 40 +++++ .../resources/pgp_private_key_passphrase.txt | 1 + ballerina/tests/resources/private_key.asc | 99 ++++++++++++ ballerina/tests/resources/public_key.asc | 55 +++++++ ballerina/tests/test_utils.bal | 4 + build-config/resources/Ballerina.toml | 6 + native/build.gradle | 1 + .../ballerina/stdlib/crypto/CryptoUtils.java | 1 + .../stdlib/crypto/PgpDecryptionGenerator.java | 145 ++++++++++++++++++ .../stdlib/crypto/PgpEncryptionGenerator.java | 145 ++++++++++++++++++ .../stdlib/crypto/nativeimpl/Decrypt.java | 22 +++ .../stdlib/crypto/nativeimpl/Encrypt.java | 34 ++++ native/src/main/java/module-info.java | 1 + 16 files changed, 641 insertions(+) create mode 100644 ballerina/pgp_utils.bal create mode 100644 ballerina/tests/encrypt_decrypt_pgp_test.bal create mode 100644 ballerina/tests/resources/pgp_private_key_passphrase.txt create mode 100644 ballerina/tests/resources/private_key.asc create mode 100644 ballerina/tests/resources/public_key.asc create mode 100644 native/src/main/java/io/ballerina/stdlib/crypto/PgpDecryptionGenerator.java create mode 100644 native/src/main/java/io/ballerina/stdlib/crypto/PgpEncryptionGenerator.java diff --git a/ballerina/build.gradle b/ballerina/build.gradle index 48a206d6..783835b7 100644 --- a/ballerina/build.gradle +++ b/ballerina/build.gradle @@ -76,6 +76,9 @@ dependencies { externalJars(group: 'org.bouncycastle', name: 'bcutil-jdk18on', version: "${bouncycastleVersion}") { transitive = false } + externalJars(group: 'org.bouncycastle', name: 'bcpg-jdk18on', version: "${bouncycastleVersion}") { + transitive = false + } } task updateTomlFiles { diff --git a/ballerina/encrypt_decrypt.bal b/ballerina/encrypt_decrypt.bal index ae4b1986..895f6940 100644 --- a/ballerina/encrypt_decrypt.bal +++ b/ballerina/encrypt_decrypt.bal @@ -243,3 +243,41 @@ public isolated function decryptAesGcm(byte[] input, byte[] key, byte[] iv, AesP name: "decryptAesGcm", 'class: "io.ballerina.stdlib.crypto.nativeimpl.Decrypt" } external; + +# Returns the PGP-encrypted value for the given data. +# ```ballerina +# byte[] message = "Hello Ballerina!".toBytes(); +# byte[] publicKey = check io:fileReadBytes("public_key.asc"); // provide the path to the public key +# byte[] cipherText = check crypto:encryptPgp(message, publicKey); +# ``` +# +# + input - The content to be encrypted +# + key - Public key +# + options - PGP encryption options +# + return - Encrypted data or else a `crypto:Error` if the key is invalid +public isolated function encryptPgp(byte[] input, byte[] key, *PgpEncryptionOptions options) + returns byte[]|Error = @java:Method { + name: "encryptPgp", + 'class: "io.ballerina.stdlib.crypto.nativeimpl.Encrypt" +} external; + +# Returns the PGP-decrypted value for the given PGP-encrypted data. +# ```ballerina +# byte[] message = "Hello Ballerina!".toBytes(); +# byte[] publicKey = check io:fileReadBytes("public_key.asc"); // provide the path to the public key +# byte[] cipherText = check crypto:encryptPgp(message, publicKey); +# +# byte[] privateKey = check io:fileReadBytes("private_key.asc"); // provide the path to the private key +# byte[] passphrase = check io:fileReadBytes("pass_phrase.txt"); // provide the path to the passphrase +# byte[] decryptedMessage = check crypto:decryptPgp(cipherText, privateKey, passphrase); +# ``` +# +# + input - The encrypted content to be decrypted +# + key - Private key +# + 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[] input, byte[] key, byte[] passphrase) + returns byte[]|Error = @java:Method { + name: "decryptPgp", + 'class: "io.ballerina.stdlib.crypto.nativeimpl.Decrypt" +} external; diff --git a/ballerina/pgp_utils.bal b/ballerina/pgp_utils.bal new file mode 100644 index 00000000..b787806d --- /dev/null +++ b/ballerina/pgp_utils.bal @@ -0,0 +1,46 @@ +// 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. + +public type PgpEncryptionOptions record {| + CompressionAlgorithmTags compressionAlgorithm = ZIP; + SymmetricKeyAlgorithmTags symmetricKeyAlgorithm = AES_256; + boolean armor = true; + boolean withIntegrityCheck = true; +|}; + +public enum CompressionAlgorithmTags { + UNCOMPRESSED = "0", + ZIP = "1", + ZLIB = "2", + BZIP2= "3" +} + +public enum SymmetricKeyAlgorithmTags { + NULL = "0", + IDEA = "1", + TRIPLE_DES = "2", + CAST5 = "3", + BLOWFISH = "4", + SAFER = "5", + DES = "6", + AES_128 = "7", + AES_192 = "8", + AES_256 = "9", + TWOFISH = "10", + CAMELLIA_128 = "11", + CAMELLIA_192 = "12", + CAMELLIA_256 = "13" +} \ No newline at end of file diff --git a/ballerina/tests/encrypt_decrypt_pgp_test.bal b/ballerina/tests/encrypt_decrypt_pgp_test.bal new file mode 100644 index 00000000..3644fb00 --- /dev/null +++ b/ballerina/tests/encrypt_decrypt_pgp_test.bal @@ -0,0 +1,40 @@ +// Copyright (c) 2020 WSO2 Inc. (http://www.wso2.org) All Rights Reserved. +// +// WSO2 Inc. 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/test; +import ballerina/io; + +@test:Config {} +isolated function testEncryptAndDecryptWithPgp() returns error? { + byte[] message = "Ballerina crypto test ".toBytes(); + byte[] passphrase = check io:fileReadBytes(PGP_PRIVATE_KEY_PASSPHRASE_PATH); + byte[] publicKey = check io:fileReadBytes(PGP_PUBLIC_KEY_PATH); + byte[] privateKey = check io:fileReadBytes(PGP_PRIVATE_KEY_PATH); + byte[] cipherText = check encryptPgp(message, publicKey); + byte[] plainText = check decryptPgp(cipherText, privateKey, passphrase); + test:assertEquals(plainText.toBase16(), message.toBase16()); +} + +@test:Config {} +isolated function testEncryptAndDecryptWithPgpWithOptions() returns error? { + byte[] message = "Ballerina crypto test ".toBytes(); + byte[] passphrase = check io:fileReadBytes(PGP_PRIVATE_KEY_PASSPHRASE_PATH); + byte[] publicKey = check io:fileReadBytes(PGP_PUBLIC_KEY_PATH); + byte[] privateKey = check io:fileReadBytes(PGP_PRIVATE_KEY_PATH); + byte[] cipherText = check encryptPgp(message, publicKey, symmetricKeyAlgorithm = AES_128, armor = false); + byte[] plainText = check decryptPgp(cipherText, privateKey, passphrase); + test:assertEquals(plainText.toBase16(), message.toBase16()); +} diff --git a/ballerina/tests/resources/pgp_private_key_passphrase.txt b/ballerina/tests/resources/pgp_private_key_passphrase.txt new file mode 100644 index 00000000..9b60eb8e --- /dev/null +++ b/ballerina/tests/resources/pgp_private_key_passphrase.txt @@ -0,0 +1 @@ +qCr3bv@5mj5n4eY \ No newline at end of file diff --git a/ballerina/tests/resources/private_key.asc b/ballerina/tests/resources/private_key.asc new file mode 100644 index 00000000..fa91919a --- /dev/null +++ b/ballerina/tests/resources/private_key.asc @@ -0,0 +1,99 @@ +-----BEGIN PGP PRIVATE KEY BLOCK----- +Version: Keybase OpenPGP v2.0.76 +Comment: https://keybase.io/crypto + +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 +4WbzDTObZg6ttIYG6+s= +=4NKk +-----END PGP PRIVATE KEY BLOCK----- diff --git a/ballerina/tests/resources/public_key.asc b/ballerina/tests/resources/public_key.asc new file mode 100644 index 00000000..6c88e615 --- /dev/null +++ b/ballerina/tests/resources/public_key.asc @@ -0,0 +1,55 @@ +-----BEGIN PGP PUBLIC KEY BLOCK----- +Version: Keybase OpenPGP v2.0.76 +Comment: https://keybase.io/crypto + +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 +=DRCu +-----END PGP PUBLIC KEY BLOCK----- diff --git a/ballerina/tests/test_utils.bal b/ballerina/tests/test_utils.bal index 4aeb36fe..59a1e2ba 100644 --- a/ballerina/tests/test_utils.bal +++ b/ballerina/tests/test_utils.bal @@ -33,3 +33,7 @@ const string 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 string PGP_PUBLIC_KEY_PATH = "tests/resources/public_key.asc"; +const string PGP_PRIVATE_KEY_PATH = "tests/resources/private_key.asc"; +const string PGP_PRIVATE_KEY_PASSPHRASE_PATH = "tests/resources/pgp_private_key_passphrase.txt"; diff --git a/build-config/resources/Ballerina.toml b/build-config/resources/Ballerina.toml index 2dadb32c..2f59bdae 100644 --- a/build-config/resources/Ballerina.toml +++ b/build-config/resources/Ballerina.toml @@ -35,3 +35,9 @@ groupId = "org.bouncycastle" artifactId = "bcutil-jdk18on" version = "@bouncycastle.version@" path = "./lib/bcutil-jdk18on-@bouncycastle.version@.jar" + +[[platform.java17.dependency]] +groupId = "org.bouncycastle" +artifactId = "bcpg-jdk18on" +version = "@bouncycastle.version@" +path = "./lib/bcpg-jdk18on-@bouncycastle.version@.jar" diff --git a/native/build.gradle b/native/build.gradle index 5fbf4ee0..ed242952 100644 --- a/native/build.gradle +++ b/native/build.gradle @@ -32,6 +32,7 @@ dependencies { implementation group: 'org.bouncycastle', name: 'bcpkix-jdk18on', version: "${bouncycastleVersion}" implementation group: 'org.bouncycastle', name: 'bcprov-jdk18on', version: "${bouncycastleVersion}" implementation group: 'org.bouncycastle', name: 'bcutil-jdk18on', version: "${bouncycastleVersion}" + implementation group: 'org.bouncycastle', name: 'bcpg-jdk18on', version: "${bouncycastleVersion}" compileOnly group: 'org.graalvm.nativeimage', name: 'svm', version: "${nativeImageVersion}" } diff --git a/native/src/main/java/io/ballerina/stdlib/crypto/CryptoUtils.java b/native/src/main/java/io/ballerina/stdlib/crypto/CryptoUtils.java index f34e0249..d0376c35 100644 --- a/native/src/main/java/io/ballerina/stdlib/crypto/CryptoUtils.java +++ b/native/src/main/java/io/ballerina/stdlib/crypto/CryptoUtils.java @@ -448,4 +448,5 @@ private static Digest selectHash(String algorithm) { } throw CryptoUtils.createError("Unsupported algorithm: " + algorithm); } + } diff --git a/native/src/main/java/io/ballerina/stdlib/crypto/PgpDecryptionGenerator.java b/native/src/main/java/io/ballerina/stdlib/crypto/PgpDecryptionGenerator.java new file mode 100644 index 00000000..28590d86 --- /dev/null +++ b/native/src/main/java/io/ballerina/stdlib/crypto/PgpDecryptionGenerator.java @@ -0,0 +1,145 @@ +/* + * 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.creators.ValueCreator; +import org.bouncycastle.jce.provider.BouncyCastleProvider; +import org.bouncycastle.openpgp.PGPCompressedData; +import org.bouncycastle.openpgp.PGPEncryptedData; +import org.bouncycastle.openpgp.PGPEncryptedDataList; +import org.bouncycastle.openpgp.PGPException; +import org.bouncycastle.openpgp.PGPLiteralData; +import org.bouncycastle.openpgp.PGPOnePassSignatureList; +import org.bouncycastle.openpgp.PGPPrivateKey; +import org.bouncycastle.openpgp.PGPPublicKeyEncryptedData; +import org.bouncycastle.openpgp.PGPSecretKey; +import org.bouncycastle.openpgp.PGPSecretKeyRingCollection; +import org.bouncycastle.openpgp.PGPUtil; +import org.bouncycastle.openpgp.jcajce.JcaPGPObjectFactory; +import org.bouncycastle.openpgp.operator.PublicKeyDataDecryptorFactory; +import org.bouncycastle.openpgp.operator.jcajce.JcaKeyFingerprintCalculator; +import org.bouncycastle.openpgp.operator.jcajce.JcePBESecretKeyDecryptorBuilder; +import org.bouncycastle.openpgp.operator.jcajce.JcePublicKeyDataDecryptorFactoryBuilder; + +import java.io.BufferedInputStream; +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.nio.charset.StandardCharsets; +import java.security.Security; +import java.util.Iterator; +import java.util.Objects; + +public class PgpDecryptionGenerator { + + static { + if (Objects.isNull(Security.getProvider(BouncyCastleProvider.PROVIDER_NAME))) { + Security.addProvider(new BouncyCastleProvider()); + } + } + + private final char[] passCode; + private final PGPSecretKeyRingCollection pgpSecretKeyRingCollection; + + public PgpDecryptionGenerator(InputStream privateKeyIn, byte[] passCode) throws IOException, PGPException { + this.passCode = new String(passCode, StandardCharsets.UTF_8).toCharArray(); + this.pgpSecretKeyRingCollection = new PGPSecretKeyRingCollection(PGPUtil.getDecoderStream(privateKeyIn) + , new JcaKeyFingerprintCalculator()); + } + + private PGPPrivateKey findSecretKey(long keyID) throws PGPException { + PGPSecretKey pgpSecretKey = pgpSecretKeyRingCollection.getSecretKey(keyID); + return pgpSecretKey == null ? null : pgpSecretKey.extractPrivateKey(new JcePBESecretKeyDecryptorBuilder() + .setProvider(BouncyCastleProvider.PROVIDER_NAME).build(passCode)); + } + + public void decryptStream(InputStream encryptedIn, OutputStream clearOut) + 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(); + + PGPPrivateKey pgpPrivateKey = null; + PGPPublicKeyEncryptedData publicKeyEncryptedData = null; + + Iterator encryptedDataItr = pgpEncryptedDataList.getEncryptedDataObjects(); + while (pgpPrivateKey == null && encryptedDataItr.hasNext()) { + publicKeyEncryptedData = (PGPPublicKeyEncryptedData) encryptedDataItr.next(); + pgpPrivateKey = findSecretKey(publicKeyEncryptedData.getKeyID()); + } + + if (Objects.isNull(publicKeyEncryptedData)) { + throw new PGPException("Could not generate PGPPublicKeyEncryptedData object"); + } + + if (pgpPrivateKey == null) { + throw new PGPException("Could Not Extract private key"); + } + decrypt(clearOut, pgpPrivateKey, publicKeyEncryptedData); + } + + public Object decrypt(byte[] encryptedBytes) throws PGPException, IOException { + ByteArrayInputStream encryptedIn = new ByteArrayInputStream(encryptedBytes); + ByteArrayOutputStream clearOut = new ByteArrayOutputStream(); + decryptStream(encryptedIn, clearOut); + return ValueCreator.createArrayValue(clearOut.toByteArray()); + } + + static void decrypt(OutputStream clearOut, PGPPrivateKey pgpPrivateKey, + PGPPublicKeyEncryptedData publicKeyEncryptedData) 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) { + InputStream decDataStream = pgpLiteralData.getInputStream(); + byte[] buffer = new byte[1024]; + int bytesRead; + while ((bytesRead = decDataStream.read(buffer)) != -1) { + clearOut.write(buffer, 0, bytesRead); + } + clearOut.close(); + } else if (message instanceof PGPOnePassSignatureList) { + throw new PGPException("Encrypted message contains a signed message not literal data"); + } else { + throw new PGPException("Message is not a simple encrypted file - Type Unknown"); + } + // Perform the integrity check + if (publicKeyEncryptedData.isIntegrityProtected()) { + if (!publicKeyEncryptedData.verify()) { + throw new PGPException("Message failed integrity check"); + } + } + } + +} diff --git a/native/src/main/java/io/ballerina/stdlib/crypto/PgpEncryptionGenerator.java b/native/src/main/java/io/ballerina/stdlib/crypto/PgpEncryptionGenerator.java new file mode 100644 index 00000000..110430b2 --- /dev/null +++ b/native/src/main/java/io/ballerina/stdlib/crypto/PgpEncryptionGenerator.java @@ -0,0 +1,145 @@ +/* + * 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.creators.ValueCreator; +import org.bouncycastle.bcpg.ArmoredOutputStream; +import org.bouncycastle.jce.provider.BouncyCastleProvider; +import org.bouncycastle.openpgp.PGPCompressedDataGenerator; +import org.bouncycastle.openpgp.PGPEncryptedDataGenerator; +import org.bouncycastle.openpgp.PGPException; +import org.bouncycastle.openpgp.PGPLiteralData; +import org.bouncycastle.openpgp.PGPLiteralDataGenerator; +import org.bouncycastle.openpgp.PGPPublicKey; +import org.bouncycastle.openpgp.PGPPublicKeyRing; +import org.bouncycastle.openpgp.PGPPublicKeyRingCollection; +import org.bouncycastle.openpgp.PGPUtil; +import org.bouncycastle.openpgp.operator.jcajce.JcaKeyFingerprintCalculator; +import org.bouncycastle.openpgp.operator.jcajce.JcePGPDataEncryptorBuilder; +import org.bouncycastle.openpgp.operator.jcajce.JcePublicKeyKeyEncryptionMethodGenerator; + +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.security.SecureRandom; +import java.security.Security; +import java.time.LocalDateTime; +import java.time.ZoneOffset; +import java.util.Arrays; +import java.util.Date; +import java.util.Iterator; +import java.util.Objects; +import java.util.Optional; + +public class PgpEncryptionGenerator { + + static { + if (Objects.isNull(Security.getProvider(BouncyCastleProvider.PROVIDER_NAME))) { + Security.addProvider(new BouncyCastleProvider()); + } + } + + private final int compressionAlgorithm; + private final int symmetricKeyAlgorithm; + private final boolean armor; + private final boolean withIntegrityCheck; + private static final int BUFFER_SIZE = 8192; + + public PgpEncryptionGenerator(int compressionAlgorithm, int symmetricKeyAlgorithm, boolean armor, + boolean withIntegrityCheck) { + this.compressionAlgorithm = compressionAlgorithm; + this.symmetricKeyAlgorithm = symmetricKeyAlgorithm; + this.armor = armor; + this.withIntegrityCheck = withIntegrityCheck; + } + + public void encryptStream(OutputStream encryptOut, InputStream clearIn, long length, InputStream publicKeyIn) + throws IOException, PGPException { + 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); + } + OutputStream cipherOutStream = pgpEncryptedDataGenerator.open(encryptOut, new byte[BUFFER_SIZE]); + copyAsLiteralData(compressedDataGenerator.open(cipherOutStream), clearIn, length); + compressedDataGenerator.close(); + cipherOutStream.close(); + encryptOut.close(); + } + + public Object encrypt(byte[] clearData, InputStream publicKeyIn) throws PGPException, IOException { + ByteArrayInputStream inputStream = new ByteArrayInputStream(clearData); + ByteArrayOutputStream outputStream = new ByteArrayOutputStream(); + encryptStream(outputStream, inputStream, clearData.length, publicKeyIn); + return ValueCreator.createArrayValue(outputStream.toByteArray()); + } + + static PGPPublicKey getPublicKey(InputStream keyInputStream) throws IOException, PGPException { + PGPPublicKeyRingCollection pgpPublicKeyRings = new PGPPublicKeyRingCollection( + PGPUtil.getDecoderStream(keyInputStream), new JcaKeyFingerprintCalculator()); + Iterator keyRingIterator = pgpPublicKeyRings.getKeyRings(); + while (keyRingIterator.hasNext()) { + PGPPublicKeyRing pgpPublicKeyRing = keyRingIterator.next(); + Optional pgpPublicKey = extractPGPKeyFromRing(pgpPublicKeyRing); + if (pgpPublicKey.isPresent()) { + return pgpPublicKey.get(); + } + } + throw new PGPException("Invalid public key"); + } + + static void copyAsLiteralData(OutputStream outputStream, InputStream in, long length) + throws IOException { + PGPLiteralDataGenerator lData = new PGPLiteralDataGenerator(); + OutputStream pOut = lData.open(outputStream, PGPLiteralData.BINARY, PGPLiteralData.CONSOLE, + Date.from(LocalDateTime.now().toInstant(ZoneOffset.UTC)), new byte[PgpEncryptionGenerator.BUFFER_SIZE]); + byte[] buff = new byte[PgpEncryptionGenerator.BUFFER_SIZE]; + try (in) { + int len; + long totalBytesWritten = 0L; + while (totalBytesWritten <= length && (len = in.read(buff)) > 0) { + pOut.write(buff, 0, len); + totalBytesWritten += len; + } + pOut.close(); + } finally { + Arrays.fill(buff, (byte) 0); + } + } + + private static Optional extractPGPKeyFromRing(PGPPublicKeyRing pgpPublicKeyRing) { + for (PGPPublicKey publicKey : pgpPublicKeyRing) { + if (publicKey.isEncryptionKey()) { + return Optional.of(publicKey); + } + } + return Optional.empty(); + } +} 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 2632ede4..521c671d 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 @@ -22,7 +22,12 @@ import io.ballerina.runtime.api.values.BMap; import io.ballerina.stdlib.crypto.Constants; import io.ballerina.stdlib.crypto.CryptoUtils; +import io.ballerina.stdlib.crypto.PgpDecryptionGenerator; +import org.bouncycastle.openpgp.PGPException; +import java.io.ByteArrayInputStream; +import java.io.IOException; +import java.io.InputStream; import java.security.Key; import java.security.PrivateKey; import java.security.PublicKey; @@ -74,4 +79,21 @@ public static Object decryptRsaEcb(BArray inputValue, Object keys, Object paddin return CryptoUtils.rsaEncryptDecrypt(CryptoUtils.CipherMode.DECRYPT, Constants.ECB, padding.toString(), key, input, null, -1); } + + public static Object decryptPgp(BArray inputValue, BArray keyValue, BArray passphrase) { + byte[] input = inputValue.getBytes(); + byte[] key = keyValue.getBytes(); + byte[] passphraseInBytes = passphrase.getBytes(); + InputStream keyStream = new ByteArrayInputStream(key); + + try { + PgpDecryptionGenerator pgpDecryptionGenerator = new PgpDecryptionGenerator( + keyStream, + passphraseInBytes + ); + return pgpDecryptionGenerator.decrypt(input); + } 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 c36e6c06..ff3b2aac 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,11 +18,21 @@ package io.ballerina.stdlib.crypto.nativeimpl; +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.BString; +import io.ballerina.runtime.api.values.BValue; import io.ballerina.stdlib.crypto.Constants; import io.ballerina.stdlib.crypto.CryptoUtils; +import io.ballerina.stdlib.crypto.PgpEncryptionGenerator; +import org.bouncycastle.bcpg.CompressionAlgorithmTags; +import org.bouncycastle.bcpg.SymmetricKeyAlgorithmTags; +import org.bouncycastle.openpgp.PGPException; +import java.io.ByteArrayInputStream; +import java.io.IOException; +import java.io.InputStream; import java.security.Key; import java.security.PrivateKey; import java.security.PublicKey; @@ -34,6 +44,11 @@ */ public class Encrypt { + public static final String COMPRESSION_ALGORITHM = "compressionAlgorithm"; + public static final String SYMMETRIC_KEY_ALGORITHM = "symmetricKeyAlgorithm"; + public static final String ARMOR = "armor"; + public static final String WITH_INTEGRITY_CHECK = "withIntegrityCheck"; + private Encrypt() {} public static Object encryptAesCbc(BArray inputValue, BArray keyValue, BArray ivValue, Object padding) { @@ -80,4 +95,23 @@ public static Object encryptRsaEcb(BArray inputValue, Object keys, Object paddin return CryptoUtils.rsaEncryptDecrypt(CryptoUtils.CipherMode.ENCRYPT, Constants.ECB, padding.toString(), key, input, null, -1); } + + public static Object encryptPgp(BArray inputValue, BArray keyValue, BMap options) { + byte[] input = inputValue.getBytes(); + byte[] key = keyValue.getBytes(); + InputStream keyStream = new ByteArrayInputStream(key); + + PgpEncryptionGenerator pgpEncryptionGenerator = new PgpEncryptionGenerator( + Integer.parseInt(options.get(StringUtils.fromString(COMPRESSION_ALGORITHM)).toString()), + Integer.parseInt(options.get(StringUtils.fromString(SYMMETRIC_KEY_ALGORITHM)).toString()), + Boolean.parseBoolean(options.get(StringUtils.fromString(ARMOR)).toString()), + Boolean.parseBoolean(options.get(StringUtils.fromString(WITH_INTEGRITY_CHECK)).toString()) + ); + + try { + return pgpEncryptionGenerator.encrypt(input, keyStream); + } catch (PGPException | IOException e) { + return CryptoUtils.createError("Error occurred while PGP encrypt: " + e.getMessage()); + } + } } diff --git a/native/src/main/java/module-info.java b/native/src/main/java/module-info.java index d539fcf6..6fca0bb3 100644 --- a/native/src/main/java/module-info.java +++ b/native/src/main/java/module-info.java @@ -22,6 +22,7 @@ requires org.bouncycastle.pkix; requires org.graalvm.sdk; requires org.graalvm.nativeimage.builder; + requires org.bouncycastle.pg; exports io.ballerina.stdlib.crypto.nativeimpl; exports io.ballerina.stdlib.crypto.svm; }