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 CryptoAPI #539

Merged
merged 38 commits into from
Mar 27, 2024
Merged
Show file tree
Hide file tree
Changes from 17 commits
Commits
Show all changes
38 commits
Select commit Hold shift + click to select a range
9350394
Add Dilithium3 Java methods
hwupathum Feb 10, 2024
f484085
Add Dilithium3 bal methods
hwupathum Feb 10, 2024
890eff3
Add Kyber from Keystore methods
hwupathum Feb 13, 2024
0ef71fc
Add Kyber KEM methods
hwupathum Feb 13, 2024
22c33f2
Add Hybrid KEM methods
hwupathum Feb 14, 2024
71b50a6
Add hkdf Sha256
hwupathum Feb 29, 2024
df13979
Add kyber rsa hpke
hwupathum Mar 4, 2024
32ce9ff
Refactor code
hwupathum Mar 5, 2024
433718b
Add licence
hwupathum Mar 5, 2024
cde030f
Apply suggestions from code review
hwupathum Mar 19, 2024
934432b
Fix build failures
hwupathum Mar 19, 2024
0d981e6
Fix revirew comments
hwupathum Mar 20, 2024
07395b2
Change Dilithium name to ML-DSA
hwupathum Mar 20, 2024
e6e7032
Change Kyber name to ML-KEM
hwupathum Mar 20, 2024
87667d7
Add ML-DSA unit tests
hwupathum Mar 20, 2024
6d0980a
Add ML-KEM unit tests
hwupathum Mar 20, 2024
798af72
Improve code coverage
hwupathum Mar 21, 2024
a96c026
[Automated] Update the native jar versions
hwupathum Mar 21, 2024
194fa5a
Apply suggestions from code review
hwupathum Mar 21, 2024
53d18df
Update reflect-config.json for BouncyCastle version bump
hwupathum Mar 21, 2024
abaafca
Refactor getPrivateKey method
hwupathum Mar 22, 2024
bb6d435
Add encrypted PrivateKey for Kyber
hwupathum Mar 22, 2024
84e8a9f
Rename hpke method names
hwupathum Mar 22, 2024
14ad720
[Automated] Update the native jar versions
ashendes Mar 22, 2024
a9714a4
PQC crypto unit tests added.
ashendes Mar 25, 2024
c443312
Tests updated.
ashendes Mar 25, 2024
26ae1af
Merge pull request #1 from ashendes/pqc
hwupathum Mar 25, 2024
c62d3df
Improve code coverage
hwupathum Mar 25, 2024
eccb010
Merge branch 'master' into pqc
hwupathum Mar 26, 2024
d16bf78
Update GraalVM configurations
hwupathum Mar 26, 2024
c1e59b8
Merge remote-tracking branch 'origin/pqc' into pqc
hwupathum Mar 26, 2024
8a78ddb
[Automated] Update the native jar versions
hwupathum Mar 26, 2024
de42504
Bump version to 2.7.0
hwupathum Mar 26, 2024
244e228
Update CryptoAPI specs
hwupathum Mar 26, 2024
c9b48af
Update method descriptions
hwupathum Mar 26, 2024
340b8af
Updated changelog
hwupathum Mar 26, 2024
4d7d924
Add newline
hwupathum Mar 26, 2024
8afab76
Add separate functions to add crypto providers
hwupathum Mar 27, 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
144 changes: 144 additions & 0 deletions ballerina/hpke.bal
Original file line number Diff line number Diff line change
@@ -0,0 +1,144 @@
// 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.

# Represent the supported symmetric key sizes for AES algorithm.
public type AesKeySize 16|24|32;

# Represents the encapsulated secret and the ciphertext used in Hybrid Public Key Encryption (HPKE).
#
# + encapsulatedSecret - The encapsulated secret
# + cipherText - The encrypted data
public type HybridEncryptionResult record {|
byte[] encapsulatedSecret;
byte[] cipherText;
|};

# Returns the ML-KEM-768-HPKE-encrypted value for the given data.
# ```ballerina
# string input = "Hello Ballerina";
# byte[] data = input.toBytes();
# crypto:KeyStore keyStore = {
# path: "/path/to/keyStore.p12",
# password: "keyStorePassword"
# };
# crypto:PublicKey publicKey = check crypto:decodeMlKem768PublicKeyFromTrustStore(keyStore, "keyAlias");
# crypto:HybridEncryptionResult encryptionResult = crypto:encryptMlKem768Hpke(data, publicKey);
# ```
# + input - The content to be encrypted
# + publicKey - Public key used for encryption
# + symmetricKeySize - The length of the symmetric key (in bytes)
# + return - Encrypted data or else a `crypto:Error` if an error occurs
public isolated function encryptMlKem768Hpke(byte[] input, PublicKey publicKey, AesKeySize symmetricKeySize = 32) returns HybridEncryptionResult|Error {
EncapsulationResult encapsulationResult = check encapsulateMlKem768(publicKey);
byte[] sharedSecret = check hkdfSha256(encapsulationResult.sharedSecret, symmetricKeySize);
byte[] encapsulatedSecret = encapsulationResult.encapsulatedSecret;
byte[] ciphertext = check encryptAesEcb(input, sharedSecret);
return {
encapsulatedSecret: encapsulatedSecret,
cipherText: ciphertext
};
}

# Returns the ML-KEM-768-HPKE-decrypted value for the given encrypted data.
# ```ballerina
# string input = "Hello Ballerina";
# byte[] data = input.toBytes();
# crypto:KeyStore keyStore = {
# path: "/path/to/keyStore.p12",
# password: "keyStorePassword"
# };
# crypto:PublicKey publicKey = check crypto:decodeMlKem768PublicKeyFromTrustStore(keyStore, "keyAlias");
# crypto:HybridEncryptionResult encryptionResult = crypto:encryptMlKem768Hpke(data, publicKey);
# byte[] cipherText = encryptionResult.cipherText;
# byte[] encapsulatedKey = encryptionResult.encapsulatedSecret;
# crypto:PrivateKey privateKey = check crypto:decodeMlKem768PrivateKeyFromKeyStore(keyStore, "keyAlias");
# byte[] decryptedData = check crypto:decryptMlKem768Hpke(cipherText, encapsulatedKey, privateKey);
# ```
# + input - The content to be decrypted
# + encapsulatedKey - The encapsulated secret
# + privateKey - The MlKem private key used for decryption
# + length - The length of the output (in bytes)
# + return - Decrypted data or else a `crypto:Error` if error occurs
public isolated function decryptMlKem768Hpke(byte[] input, byte[] encapsulatedKey, PrivateKey privateKey, int length = 32) returns byte[]|Error {
byte[] key = check decapsulateMlKem768(encapsulatedKey, privateKey);
key = check hkdfSha256(key, length);
return check decryptAesEcb(input, key);
}

# Returns the RSA-ML-KEM-768-HPKE-encrypted value for the given data.
# ```ballerina
# string input = "Hello Ballerina";
# byte[] data = input.toBytes();
# crypto:KeyStore mlkemKeyStore = {
# path: "/path/to/mlkem/keyStore.p12",
# password: "keyStorePassword"
# };
# crypto:KeyStore rsaKeyStore = {
# path: "/path/to/rsa/keyStore.p12",
# password: "keyStorePassword"
# };
# crypto:PublicKey mlkemPublicKey = check crypto:decodeMlKem768PublicKeyFromTrustStore(mlkemKeyStore, "keyAlias");
# crypto:PublicKey rsaPublicKey = check crypto:decodeRsaPublicKeyFromTrustStore(rsaKeyStore, "keyAlias");
# crypto:HybridEncryptionResult encryptionResult = crypto:encryptRsaMlKem768Hpke(data, rsaPublicKey, mlkemPublicKey);
# ```
# + input - The content to be encrypted
# + rsaPublicKey - The RSA public key used for encryption
# + mlkemPublicKey - The MlKem public key used for encryption
# + symmetricKeySize - The length of the symmetric key (in bytes)
# + return - Encrypted data or else a `crypto:Error` if an error occurs
public isolated function encryptRsaMlKem768Hpke(byte[] input, PublicKey rsaPublicKey, PublicKey mlkemPublicKey, AesKeySize symmetricKeySize = 32) returns HybridEncryptionResult|Error {
EncapsulationResult hybridEncapsulationResult = check encapsulateRsaKemMlKem768(rsaPublicKey, mlkemPublicKey);
byte[] sharedSecret = check hkdfSha256(hybridEncapsulationResult.sharedSecret, symmetricKeySize);
byte[] encapsulatedSecret = hybridEncapsulationResult.encapsulatedSecret;
byte[] ciphertext = check encryptAesEcb(input, sharedSecret);
return {
encapsulatedSecret: encapsulatedSecret,
cipherText: ciphertext
};
hwupathum marked this conversation as resolved.
Show resolved Hide resolved
}

# Returns the RSA-ML-KEM-768-HPKE-decrypted value for the given encrypted data.
# ```ballerina
# string input = "Hello Ballerina";
# byte[] data = input.toBytes();
# crypto:KeyStore mlkemKeyStore = {
# path: "/path/to/mlkem/keyStore.p12",
# password: "keyStorePassword"
# };
# crypto:KeyStore rsaKeyStore = {
# path: "/path/to/rsa/keyStore.p12",
# password: "keyStorePassword"
# };
# crypto:PublicKey mlkemPublicKey = check crypto:decodeMlKem768PublicKeyFromTrustStore(mlkemKeyStore, "keyAlias");
# crypto:PublicKey rsaPublicKey = check crypto:decodeRsaPublicKeyFromTrustStore(rsaKeyStore, "keyAlias");
# crypto:HybridEncryptionResult encryptionResult = crypto:encryptRsaMlKem768Hpke(data, rsaPublicKey, mlkemPublicKey);
# byte[] cipherText = encryptionResult.cipherText;
# byte[] encapsulatedKey = encryptionResult.encapsulatedSecret;
# crypto:PrivateKey mlkemPrivateKey = check crypto:decodeMlKem768PrivateKeyFromKeyStore(mlkemKeyStore, "keyAlias");
# crypto:PrivateKey rsaPrivateKey = check crypto:decodeRsaPrivateKeyFromKeyStore(rsaKeyStore, "keyAlias");
# byte[] decryptedData = check crypto:decryptRsaMlKem768Hpke(cipherText, encapsulatedKey, rsaPrivateKey, mlkemPrivateKey);
# ```
# + input - The content to be decrypted
# + encapsulatedKey - The encapsulated secret
# + rsaPrivateKey - The RSA private key used for decryption
# + mlkemPrivateKey - The MlKem private key used for decryption
# + length - The length of the output (in bytes)
# + return - Decrypted data or else a `crypto:Error` if error occurs
public isolated function decryptRsaMlKem768Hpke(byte[] input, byte[] encapsulatedKey, PrivateKey rsaPrivateKey, PrivateKey mlkemPrivateKey, int length = 32) returns byte[]|Error {
byte[] key = check decapsulateRsaKemMlKem768(encapsulatedKey, rsaPrivateKey, mlkemPrivateKey);
key = check hkdfSha256(key, length);
return check decryptAesEcb(input, key);
}
29 changes: 29 additions & 0 deletions ballerina/kdf.bal
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
// 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.
hwupathum marked this conversation as resolved.
Show resolved Hide resolved

import ballerina/jballerina.java;

# Returns HKDF (HMAC-based Key Derivation Function) using SHA-256 as the hash function.
#
# + input - The input key material to derive the key from
# + length - The length of the output keying material (OKM) in bytes
# + salt - Optional salt value, a non-secret random value
# + info - Optional context and application-specific information
# + return - The derived keying material (OKM) of the specified length
public isolated function hkdfSha256(byte[] input, int length, byte[] salt = [0], byte[] info = []) returns byte[]|Error = @java:Method {
name: "hkdfSha256",
'class: "io.ballerina.stdlib.crypto.nativeimpl.Kdf"
} external;
161 changes: 161 additions & 0 deletions ballerina/kem.bal
Original file line number Diff line number Diff line change
@@ -0,0 +1,161 @@
// 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;

# Represents the shared secret and its encapsulation used in Key Encapsulation Mechanism (KEM).
#
# + encapsulatedSecret - Encapsulated secret
# + sharedSecret - Shared secret
public type EncapsulationResult record {|
byte[] encapsulatedSecret;
byte[] sharedSecret;
|};

# Creates a shared secret and its encapsulation used for Key Encapsulation Mechanism (KEM) using the ML-KEM-768 (Kyber768) public key.
# ```ballerina
# crypto:KeyStore keyStore = {
# path: "/path/to/keyStore.p12",
# password: "keyStorePassword"
# };
# crypto:PublicKey publicKey = check crypto:decodeMlKem768PublicKeyFromTrustStore(keyStore, "keyAlias", "keyStorePassword");
# crypto:EncapsulationResult encapsulationResult = check crypto:encapsulateMlKem768(publicKey);
# ```
# + publicKey - Public key
# + return - Encapsulated secret or else a `crypto:Error` if the public key is invalid
public isolated function encapsulateMlKem768(PublicKey publicKey)
returns EncapsulationResult|Error = @java:Method {
name: "encapsulateMlKem768",
'class: "io.ballerina.stdlib.crypto.nativeimpl.Kem"
} external;

# Decapsulates the ML-KEM-768 (Kyber768) shared secret used for Key Encapsulation Mechanism (KEM) from the given encapsulation for the given data.
# ```ballerina
# crypto:KeyStore keyStore = {
# path: "/path/to/keyStore.p12",
# password: "keyStorePassword"
# };
# crypto:PublicKey publicKey = check crypto:decodeMlKem768PublicKeyFromTrustStore(keyStore, "keyAlias");
# crypto:EncapsulationResult encapsulationResult = check crypto:encapsulateMlKem768(publicKey);
# byte[] encapsulatedSecret = encapsulationResult.encapsulatedSecret;
# crypto:PrivateKey privateKey = check crypto:decodeMlKem768PrivateKeyFromKeyStore(keyStore, "keyAlias");
# byte[] sharedSecret = check crypto:decapsulateMlKem768(encapsulatedSecret, privateKey);
# ```
# + encapsulatedSecret - Encapsulated secret
# + privateKey - Private key
# + return - Shared secret or else a `crypto:Error` if the encapsulatedSecret or the private key is invalid
public isolated function decapsulateMlKem768(byte[] encapsulatedSecret, PrivateKey privateKey)
returns byte[]|Error = @java:Method {
name: "decapsulateMlKem768",
'class: "io.ballerina.stdlib.crypto.nativeimpl.Kem"
} external;

# Creates a shared secret and its encapsulation used for Key Encapsulation Mechanism (KEM) using RSA and ML-KEM-768 (Kyber768) public keys.
# ```ballerina
# crypto:KeyStore mlkemKeyStore = {
# path: "/path/to/mlkem/keyStore.p12",
# password: "keyStorePassword"
# };
# crypto:KeyStore rsaKeyStore = {
# path: "/path/to/rsa/keyStore.p12",
# password: "keyStorePassword"
# };
# crypto:PublicKey mlkemPublicKey = check crypto:decodeMlKem768PublicKeyFromTrustStore(mlkemKeyStore, "keyAlias");
# crypto:PublicKey rsaPublicKey = check crypto:decodeRsaPublicKeyFromTrustStore(rsaKeyStore, "keyAlias");
# crypto:EncapsulationResult encapsulationResult = check crypto:encapsulateRsaKemMlKem768(rsaPublicKey, mlkemPublicKey);
# ```
# + rsaPublicKey - RSA public key
# + mlkemPublicKey - MlKem public key
# + return - Encapsulated secret or else a `crypto:Error` if the keysize or public keys are invalid
public isolated function encapsulateRsaKemMlKem768(PublicKey rsaPublicKey, PublicKey mlkemPublicKey)
returns EncapsulationResult|Error {
EncapsulationResult rsaEncapsulationResult = check encapsulateRsaKem(rsaPublicKey);
EncapsulationResult mlkemEncapsulationResult = check encapsulateMlKem768(mlkemPublicKey);
EncapsulationResult encapsulationResult = {
sharedSecret: [...rsaEncapsulationResult.sharedSecret, ...mlkemEncapsulationResult.sharedSecret],
encapsulatedSecret: [...rsaEncapsulationResult.encapsulatedSecret, ...mlkemEncapsulationResult.encapsulatedSecret]
};
return encapsulationResult;
}

# Decapsulates the shared secret used for Key Encapsulation Mechanism (KEM) using RSA and ML-KEM-768 (Kyber768) private keys.
# ```ballerina
# crypto:KeyStore mlkemKeyStore = {
# path: "/path/to/mlkem/keyStore.p12",
# password: "keyStorePassword"
# };
# crypto:KeyStore rsaKeyStore = {
# path: "/path/to/rsa/keyStore.p12",
# password: "keyStorePassword"
# };
# crypto:PublicKey mlkemPublicKey = check crypto:decodeMlKem768PublicKeyFromTrustStore(mlkemKeyStore, "keyAlias");
# crypto:PublicKey rsaPublicKey = check crypto:decodeRsaPublicKeyFromTrustStore(rsaKeyStore, "keyAlias");
# crypto:EncapsulationResult encapsulationResult = check crypto:encapsulateRsaKemMlKem768(rsaPublicKey, mlkemPublicKey);
# byte[] encapsulatedSecret = encapsulationResult.encapsulatedSecret;
# crypto:PrivateKey mlkemPrivateKey = check crypto:decodeMlKem768PrivateKeyFromKeyStore(mlkemKeyStore, "keyAlias");
# crypto:PrivateKey rsaPrivateKey = check crypto:decodeRsaPrivateKeyFromKeyStore(rsaKeyStore, "keyAlias");
# byte[] sharedSecret = check crypto:decapsulateRsaKemMlKem768(encapsulatedSecret, rsaPrivateKey, mlkemPrivateKey);
# ```
# + encapsulatedSecret - Encapsulated secret
# + rsaPrivateKey - RSA private key
# + mlkemPrivateKey - MlKem private key
# + return - Shared secret or else a `crypto:Error` if the keysize or private keys are invalid
public isolated function decapsulateRsaKemMlKem768(byte[] encapsulatedSecret, PrivateKey rsaPrivateKey, PrivateKey mlkemPrivateKey)
returns byte[]|Error {
byte[] rsaEncapsulatedSecret = encapsulatedSecret.slice(0, 256);
byte[] mlkemEncapsulatedSecret = encapsulatedSecret.slice(256);
byte[] rsaSharedSecret = check decapsulateRsaKem(rsaEncapsulatedSecret, rsaPrivateKey);
byte[] mlkemSharedSecret = check decapsulateMlKem768(mlkemEncapsulatedSecret, mlkemPrivateKey);
return [...rsaSharedSecret, ...mlkemSharedSecret];
}

# Creates a shared secret and its encapsulation used for Key Encapsulation Mechanism (KEM) using the RSA public key.
# ```ballerina
# crypto:KeyStore keyStore = {
# path: "/path/to/keyStore.p12",
# password: "keyStorePassword"
# };
# crypto:PublicKey publicKey = check crypto:decodeRsaPublicKeyFromTrustStore(keyStore, "keyAlias");
# crypto:EncapsulationResult encapsulationResult = check crypto:encapsulateRsaKem(publicKey);
# ```
# + publicKey - Public key
# + return - Encapsulated secret or else a `crypto:Error` if the public key is invalid
public isolated function encapsulateRsaKem(PublicKey publicKey)
returns EncapsulationResult|Error = @java:Method {
name: "encapsulateRsaKem",
'class: "io.ballerina.stdlib.crypto.nativeimpl.Kem"
} external;

# Decapsulates the shared secret used for Key Encapsulation Mechanism (KEM) from the given encapsulation for the given data.
# ```ballerina
# crypto:KeyStore keyStore = {
# path: "/path/to/keyStore.p12",
# password: "keyStorePassword"
# };
# crypto:PublicKey publicKey = check crypto:decodeRsaPublicKeyFromTrustStore(keyStore, "keyAlias");
# crypto:EncapsulationResult encapsulationResult = check crypto:encapsulateRsaKem(publicKey);
# byte[] encapsulatedSecret = encapsulationResult.encapsulatedSecret;
# crypto:PrivateKey privateKey = check crypto:decodeRsaPrivateKeyFromKeyStore(keyStore, "keyAlias");
# byte[] sharedSecret = check crypto:decapsulateRsaKem(encapsulatedSecret, privateKey);
# ```
# + encapsulatedSecret - Encapsulated secret
# + privateKey - Private key
# + return - Shared secret or else a `crypto:Error` if the encapsulatedSecret or the private key is invalid
public isolated function decapsulateRsaKem(byte[] encapsulatedSecret, PrivateKey privateKey)
returns byte[]|Error = @java:Method {
name: "decapsulateRsaKem",
'class: "io.ballerina.stdlib.crypto.nativeimpl.Kem"
} external;
Loading