Skip to content
Closed
Show file tree
Hide file tree
Changes from 4 commits
Commits
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
58 changes: 51 additions & 7 deletions ballerina/sign_verify.bal
Original file line number Diff line number Diff line change
Expand Up @@ -136,6 +136,26 @@ public isolated function signRsaSha512(byte[] input, PrivateKey privateKey) retu
'class: "io.ballerina.stdlib.crypto.nativeimpl.Sign"
} external;

# Returns the RSASSA-PSS with SHA-256 based signature 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:PrivateKey privateKey = check crypto:decodeRsaPrivateKeyFromKeyStore(keyStore, "keyAlias", "keyPassword");
# byte[] signature = check crypto:signRsaSsaPss256(data, privateKey);
# ```
#
# + input - The content to be signed
# + privateKey - Private key used for signing
# + return - The generated signature or else a `crypto:Error` if the private key is invalid
public isolated function signRsaSsaPss256(byte[] input, PrivateKey privateKey) returns byte[]|Error = @java:Method {
name: "signRsaSsaPss256",
'class: "io.ballerina.stdlib.crypto.nativeimpl.Sign"
} external;

# Returns the SHA384withECDSA based signature value for the given data.
# ```ballerina
# string input = "Hello Ballerina";
Expand Down Expand Up @@ -195,7 +215,7 @@ public isolated function signSha256withEcdsa(byte[] input, PrivateKey privateKey
# + publicKey - Public key used for verification
# + return - Validity of the signature or else a `crypto:Error` if the public key is invalid
public isolated function verifyRsaMd5Signature(byte[] data, byte[] signature, PublicKey publicKey)
returns boolean|Error = @java:Method {
returns boolean|Error = @java:Method {
name: "verifyRsaMd5Signature",
'class: "io.ballerina.stdlib.crypto.nativeimpl.Sign"
} external;
Expand All @@ -219,7 +239,7 @@ public isolated function verifyRsaMd5Signature(byte[] data, byte[] signature, Pu
# + publicKey - Public key used for verification
# + return - Validity of the signature or else a `crypto:Error` if the public key is invalid
public isolated function verifyMlDsa65Signature(byte[] data, byte[] signature, PublicKey publicKey)
returns boolean|Error = @java:Method {
returns boolean|Error = @java:Method {
name: "verifyMlDsa65Signature",
'class: "io.ballerina.stdlib.crypto.nativeimpl.Sign"
} external;
Expand Down Expand Up @@ -267,7 +287,7 @@ public isolated function verifyRsaSha1Signature(byte[] data, byte[] signature, P
# + publicKey - Public key used for verification
# + return - Validity of the signature or else a `crypto:Error` if the public key is invalid
public isolated function verifyRsaSha256Signature(byte[] data, byte[] signature, PublicKey publicKey)
returns boolean|Error = @java:Method {
returns boolean|Error = @java:Method {
name: "verifyRsaSha256Signature",
'class: "io.ballerina.stdlib.crypto.nativeimpl.Sign"
} external;
Expand All @@ -291,7 +311,7 @@ public isolated function verifyRsaSha256Signature(byte[] data, byte[] signature,
# + publicKey - Public key used for verification
# + return - Validity of the signature or else a `crypto:Error` if the public key is invalid
public isolated function verifyRsaSha384Signature(byte[] data, byte[] signature, PublicKey publicKey)
returns boolean|Error = @java:Method {
returns boolean|Error = @java:Method {
name: "verifyRsaSha384Signature",
'class: "io.ballerina.stdlib.crypto.nativeimpl.Sign"
} external;
Expand All @@ -315,11 +335,35 @@ public isolated function verifyRsaSha384Signature(byte[] data, byte[] signature,
# + publicKey - Public key used for verification
# + return - Validity of the signature or else a `crypto:Error` if the public key is invalid
public isolated function verifyRsaSha512Signature(byte[] data, byte[] signature, PublicKey publicKey)
returns boolean|Error = @java:Method {
returns boolean|Error = @java:Method {
name: "verifyRsaSha512Signature",
'class: "io.ballerina.stdlib.crypto.nativeimpl.Sign"
} external;

# Verifies the RSASSA-PSS with SHA-256 based signature.
# ```ballerina
# string input = "Hello Ballerina";
# byte[] data = input.toBytes();
# crypto:KeyStore keyStore = {
# path: "/path/to/keyStore.p12",
# password: "keyStorePassword"
# };
# crypto:PrivateKey privateKey = check crypto:decodeRsaPrivateKeyFromKeyStore(keyStore, "keyAlias", "keyPassword");
# byte[] signature = check crypto:signRsaSsaPss256(data, privateKey);
# crypto:PublicKey publicKey = check crypto:decodeRsaPublicKeyFromTrustStore(keyStore, "keyAlias");
# boolean validity = check crypto:verifyRsaSsaPss256Signature(data, signature, publicKey);
# ```
#
# + data - The content to be verified
# + signature - Signature value
# + publicKey - Public key used for verification
# + return - Validity of the signature or else a `crypto:Error` if the public key is invalid
public isolated function verifyRsaSsaPss256Signature(byte[] data, byte[] signature, PublicKey publicKey)
returns boolean|Error = @java:Method {
name: "verifyRsaSsaPss256Signature",
'class: "io.ballerina.stdlib.crypto.nativeimpl.Sign"
} external;

# Verifies the SHA384withECDSA based signature.
# ```ballerina
# string input = "Hello Ballerina";
Expand All @@ -339,7 +383,7 @@ public isolated function verifyRsaSha512Signature(byte[] data, byte[] signature,
# + publicKey - Public key used for verification
# + return - Validity of the signature or else a `crypto:Error` if the public key is invalid
public isolated function verifySha384withEcdsaSignature(byte[] data, byte[] signature, PublicKey publicKey)
returns boolean|Error = @java:Method {
returns boolean|Error = @java:Method {
name: "verifySha384withEcdsaSignature",
'class: "io.ballerina.stdlib.crypto.nativeimpl.Sign"
} external;
Expand All @@ -363,7 +407,7 @@ public isolated function verifySha384withEcdsaSignature(byte[] data, byte[] sign
# + publicKey - Public key used for verification
# + return - Validity of the signature or else a `crypto:Error` if the public key is invalid
public isolated function verifySha256withEcdsaSignature(byte[] data, byte[] signature, PublicKey publicKey)
returns boolean|Error = @java:Method {
returns boolean|Error = @java:Method {
name: "verifySha256withEcdsaSignature",
'class: "io.ballerina.stdlib.crypto.nativeimpl.Sign"
} external;
47 changes: 42 additions & 5 deletions ballerina/tests/sign_verify_test.bal
Original file line number Diff line number Diff line change
Expand Up @@ -106,6 +106,18 @@ isolated function testSignRsaSha512() returns Error? {
test:assertEquals(sha512Signature.toBase16(), expectedSha512Signature);
}

@test:Config {}
isolated function testSignRsaSsaPss256() returns Error? {
byte[] payload = "Ballerina test".toBytes();
KeyStore keyStore = {
path: KEYSTORE_PATH,
password: "ballerina"
};
PrivateKey privateKey = check decodeRsaPrivateKeyFromKeyStore(keyStore, "ballerina", "ballerina");
byte[] pssSignature = check signRsaSsaPss256(payload, privateKey);
test:assertTrue(pssSignature.length() > 0);
}

@test:Config {}
isolated function testSignMlDsa65() returns Error? {
byte[] payload = "Ballerina test".toBytes();
Expand Down Expand Up @@ -204,7 +216,7 @@ isolated function testSignMlDsa65() returns Error? {
@test:Config {}
isolated function testSignRsaMd5WithInvalidKey() {
byte[] payload = "Ballerina test".toBytes();
PrivateKey privateKey = {algorithm:"RSA"};
PrivateKey privateKey = {algorithm: "RSA"};
byte[]|Error result = signRsaMd5(payload, privateKey);
if result is Error {
test:assertTrue(result.message().includes("Uninitialized private key:"));
Expand All @@ -216,7 +228,7 @@ isolated function testSignRsaMd5WithInvalidKey() {
@test:Config {}
isolated function testSignRsaSha1WithInvalidKey() {
byte[] payload = "Ballerina test".toBytes();
PrivateKey privateKey = {algorithm:"RSA"};
PrivateKey privateKey = {algorithm: "RSA"};
byte[]|Error result = signRsaSha1(payload, privateKey);
if result is Error {
test:assertTrue(result.message().includes("Uninitialized private key:"));
Expand All @@ -228,7 +240,7 @@ isolated function testSignRsaSha1WithInvalidKey() {
@test:Config {}
isolated function testSignRsaSha256WithInvalidKey() {
byte[] payload = "Ballerina test".toBytes();
PrivateKey privateKey = {algorithm:"RSA"};
PrivateKey privateKey = {algorithm: "RSA"};
byte[]|Error result = signRsaSha256(payload, privateKey);
if result is Error {
test:assertTrue(result.message().includes("Uninitialized private key:"));
Expand All @@ -240,7 +252,7 @@ isolated function testSignRsaSha256WithInvalidKey() {
@test:Config {}
isolated function testSignRsaSha384WithInvalidKey() {
byte[] payload = "Ballerina test".toBytes();
PrivateKey privateKey = {algorithm:"RSA"};
PrivateKey privateKey = {algorithm: "RSA"};
byte[]|Error result = signRsaSha384(payload, privateKey);
if result is Error {
test:assertTrue(result.message().includes("Uninitialized private key:"));
Expand All @@ -252,7 +264,7 @@ isolated function testSignRsaSha384WithInvalidKey() {
@test:Config {}
isolated function testSignRsaSha512WithInvalidKey() {
byte[] payload = "Ballerina test".toBytes();
PrivateKey privateKey = {algorithm:"RSA"};
PrivateKey privateKey = {algorithm: "RSA"};
byte[]|Error result = signRsaSha512(payload, privateKey);
if result is Error {
test:assertTrue(result.message().includes("Uninitialized private key:"));
Expand All @@ -261,6 +273,18 @@ isolated function testSignRsaSha512WithInvalidKey() {
}
}

@test:Config {}
isolated function testSignRsaSsaPss256WithInvalidKey() {
byte[] payload = "Ballerina test".toBytes();
PrivateKey privateKey = {algorithm: "RSA"};
byte[]|Error result = signRsaSsaPss256(payload, privateKey);
if result is Error {
test:assertTrue(result.message().includes("Uninitialized private key:"));
} else {
test:assertFail("Expected error not found.");
}
}

@test:Config {}
isolated function testSignMlDsa65WithInvalidKey() {
byte[] payload = "Ballerina test".toBytes();
Expand Down Expand Up @@ -338,6 +362,19 @@ isolated function testVerifyRsaSha512() returns Error? {
test:assertTrue(check verifyRsaSha512Signature(payload, sha512Signature, publicKey));
}

@test:Config {}
isolated function testVerifyRsaSsaPss256() returns Error? {
byte[] payload = "Ballerina test".toBytes();
KeyStore keyStore = {
path: KEYSTORE_PATH,
password: "ballerina"
};
PrivateKey privateKey = check decodeRsaPrivateKeyFromKeyStore(keyStore, "ballerina", "ballerina");
PublicKey publicKey = check decodeRsaPublicKeyFromTrustStore(keyStore, "ballerina");
byte[] pssSignature = check signRsaSsaPss256(payload, privateKey);
test:assertTrue(check verifyRsaSsaPss256Signature(payload, pssSignature, publicKey));
}

@test:Config {}
isolated function testVerifySha384withEcdsa() returns Error? {
byte[] payload = "Ballerina test".toBytes();
Expand Down
1 change: 1 addition & 0 deletions changelog.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
## [Unreleased]

### Added
- [Introduce support for RSASSA-PSS (PS256) signature algorithm](https://github.com/ballerina-platform/ballerina-library/issues/8292)
- [Introduce support for PBKDF2 password hashing and verification](https://github.com/ballerina-platform/ballerina-lang/issues/43926)

### Changed
Expand Down
94 changes: 94 additions & 0 deletions docs/proposals/rsassa-pss-signature-support.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
# Proposal: Introduce RSASSA-PSS (PS256) Signature Support to Ballerina Crypto Module

_Authors_: @randilt
_Reviewers_:
_Created_: 2025/10/01
_Updated_: 2025/10/01
_Issue_: [#8292](https://github.com/ballerina-platform/ballerina-library/issues/8292)

## Summary

This proposal introduces support for the RSASSA-PSS signature scheme with SHA-256 (PS256) to the Ballerina Crypto Module. Currently, only classic RSA signatures (PKCS#1 v1.5) are available, but RSASSA-PSS provides enhanced security properties and is increasingly required by modern cryptographic standards and protocols.

## Goals

- Provide support for RSASSA-PSS signature generation with SHA-256
- Provide support for RSASSA-PSS signature verification with SHA-256
- Enable higher-level modules like JWT to utilize PS256 signatures

## Motivation

The Ballerina Crypto Module currently supports several RSA signature algorithms using PKCS#1 v1.5 padding (RSA-MD5, RSA-SHA1, RSA-SHA256, RSA-SHA384, RSA-SHA512). However, RSASSA-PSS, which is defined in RFC 8017 and provides enhanced security properties, is not yet supported.

RSASSA-PSS offers several advantages over PKCS#1 v1.5:

1. **Provable Security**: RSASSA-PSS has a security proof in the random oracle model
2. **Probabilistic Signatures**: Uses random salt generation, making signatures non-deterministic
3. **Modern Standard**: Required by many modern protocols and standards, including JWT RS256 alternative (PS256)
4. **Recommended by Standards**: NIST and other standards bodies recommend RSASSA-PSS over PKCS#1 v1.5

The lack of native support for RSASSA-PSS limits the cryptographic capabilities of Ballerina applications, particularly those implementing modern security protocols that require or prefer PS256 signatures.

## Description

This proposal adds RSASSA-PSS signature generation and verification with SHA-256 to the Ballerina Crypto Module, following the same architectural patterns as existing RSA signature functions.

The key functionalities expected from this change are:

- API to generate RSASSA-PSS signatures with `crypto:signRsaSsaPss256`
- API to verify RSASSA-PSS signatures with `crypto:verifyRsaSsaPss256Signature`

### API additions

Two new APIs will be added following the existing RSA signature function patterns:

```ballerina
# Returns the RSASSA-PSS based signature value for the given data.
#
# + input - The content to be signed
# + privateKey - Private key used for signing
# + return - The generated signature or else a `crypto:Error` if the private key is invalid
public isolated function signRsaSsaPss256(byte[] input, PrivateKey privateKey) returns byte[]|Error;
```

```ballerina
# Verifies the RSASSA-PSS based signature.
#
# + data - The content to be verified
# + signature - Signature value
# + publicKey - Public key used for verification
# + return - Validity of the signature or else a `crypto:Error` if the key is invalid
public isolated function verifyRsaSsaPss256Signature(byte[] data, byte[] signature, PublicKey publicKey) returns boolean|Error;
```

### Implementation Details

The implementation follows the existing architecture:

1. **Java Native Implementation**: Uses Java's `Signature.getInstance("SHA256withRSAandMGF1")` with default PSS parameters
2. **Algorithm String**: Uses `"SHA256withRSAandMGF1"` directly as string literal, consistent with other algorithms
3. **Method Naming**: Follows the pattern `signRsaSsaPss256` and `verifyRsaSsaPss256Signature`
4. **Documentation**: Matches the style and format of existing signature functions

### PSS Parameters

The implementation uses Java's default RSASSA-PSS parameters:

- **Hash Algorithm**: SHA-256
- **MGF**: MGF1 with SHA-256
- **Salt Length**: Same as hash length (32 bytes for SHA-256)
- **Trailer Field**: 1 (standard value)

These parameters align with common RSASSA-PSS usage and provide strong security guarantees.

## Testing

The implementation includes comprehensive test coverage following the existing test patterns:

1. **Basic Signing Test**: Verifies signature generation returns valid length (probabilistic nature prevents deterministic comparison)
2. **Invalid Key Test**: Verifies proper error handling with invalid private keys
3. **Sign-Verify Test**: Tests complete workflow of signing with private key and verifying with corresponding public key

## Backward Compatibility

This addition is fully backward compatible as it only adds new functions without modifying existing APIs or behavior.
Loading