diff --git a/.gitattributes b/.gitattributes
new file mode 100644
index 0000000..1bb96bb
--- /dev/null
+++ b/.gitattributes
@@ -0,0 +1,2 @@
+# Ensure all Java files use LF.
+*.java eol=lf
diff --git a/.gitignore b/.gitignore
index 2fe7e94..1272c57 100644
--- a/.gitignore
+++ b/.gitignore
@@ -38,3 +38,6 @@ target
# Ballerina
velocity.log*
*Ballerina.lock
+
+.vscode
+config-dir
diff --git a/README.md b/README.md
index c196deb..396d97f 100644
--- a/README.md
+++ b/README.md
@@ -9,9 +9,213 @@ Ballerina SOAP Library
[![Github issues](https://img.shields.io/github/issues/ballerina-platform/ballerina-standard-library/module/soap.svg?label=Open%20Issues)](https://github.com/ballerina-platform/ballerina-standard-library/labels/module%2Fyaml)
[![codecov](https://codecov.io/gh/ballerina-platform/module-ballerina-soap/branch/master/graph/badge.svg)](https://codecov.io/gh/ballerina-platform/module-ballerina-soap)
-This library provides APIs to send an ordinary XML request to a SOAP backend by specifying the necessary details to construct a SOAP envelope.
+This module offers a set of APIs that facilitate the transmission of XML requests to a SOAP backend. It excels in managing security policies within SOAP requests, ensuring the transmission of secured SOAP envelopes. Moreover, it possesses the capability to efficiently extract data from security-applied SOAP responses.
-Soap module abstracts out the details of the creation of a SOAP envelope, headers, and the body in a SOAP message.
+SOAP module abstracts out the details of the creation of a SOAP envelope, headers, and the body in a SOAP message.
+
+## Client
+
+The `Client` is used to connect to and interact with `SOAP` endpoints.
+
+### SOAP 1.1 Client
+
+```ballerina
+import ballerina/soap:soap11;
+
+soap11:Client soapClient = check new ("http://www.dneonline.com/calculator.asmx?WSDL");
+```
+
+### SOAP 1.2 Client
+
+```ballerina
+import ballerina/soap:soap12;
+
+soap12:Client soapClient = check new ("http://www.dneonline.com/calculator.asmx?WSDL");
+```
+
+## APIs associated with SOAP
+
+- **Send & Receive**: Sends SOAP request and receives a response.
+- **Send Only**: Fires and forgets requests. Sends the request without the possibility of any response from the service.
+
+The SOAP 1.1 specification requires the inclusion of the `action` parameter as a mandatory component within its APIs. In contrast, SOAP 1.2 relaxes this requirement, making the action parameter optional.
+
+### Example: Send & Receive
+
+```ballerina
+import ballerina/soap:soap11;
+
+public function main() returns error? {
+ soap11:Client soapClient = check new ("http://www.dneonline.com/calculator.asmx?WSDL");
+
+ xml envelope = xml `
+
+
+ 2
+ 3
+
+
+ `;
+ xml|mime:Entity[] response = check soapClient->sendReceive(envelope, "http://tempuri.org/Add");
+}
+```
+
+### Example: Send Only
+
+```ballerina
+import ballerina/soap:soap11;
+
+public function main() returns error? {
+ soap11:Client soapClient = check new ("http://www.dneonline.com/calculator.asmx?WSDL");
+
+ xml envelope = xml `
+
+
+ 2
+ 3
+
+
+ `;
+ check soapClient->sendOnly(envelope, "http://tempuri.org/Add");
+}
+```
+
+## Security
+
+The SOAP client module introduces a robust framework for configuring security measures in SOAP communication. Security is a critical concern when exchanging data via web services, and this module offers comprehensive options to fortify SOAP requests and responses.
+
+There are two primary security configurations available for SOAP clients:
+
+- `inboundSecurity`: This configuration is applied to the SOAP envelope when a request is made. It includes various ws security policies such as Username Token, Timestamp Token, X509 Token, Symmetric Binding, Asymmetric Binding, and Transport Binding, either individually or in combination with each other.
+
+- `outboundSecurity`: This configuration is applied to the SOAP envelope when a response is received. Its purpose is to decrypt the data within the envelope and verify the digital signature for security validation.
+
+### Policies
+
+This library currently supports the following WS Security policies:
+
+- **Username Token**: Provides authentication through username and password credentials.
+- **Timestamp Token**: Enhances message integrity by incorporating timestamp information.
+- **X509 Token**: Allows the use of X.509 certificates for secure communication.
+- **Symmetric Binding**: Enables symmetric key-based security mechanisms.
+- **Asymmetric Binding**: Facilitates the use of asymmetric cryptography for enhanced security.
+
+These policies empower SOAP clients to enhance the security of their web service communications by selecting and implementing the appropriate security mechanisms to safeguard their SOAP envelopes.
+
+### Security Policy Configuration Types
+
+#### Inbound Security Configurations
+
+- `TimestampTokenConfig`: Represents the record for Timestamp Token policy.
+ - Fields:
+ - `int` timeToLive : The time to get expired
+
+- `UsernameTokenConfig`: Represents the record for Username Token policy.
+ - Fields:
+ - `string` username : The name of the user
+ - `string` password : The password of the user
+ - `PasswordType` passwordType : The password type of the username token
+
+- `SymmetricBindingConfig`: Represents the record for Symmetric Binding policy.
+ - Fields:
+ - `crypto:PrivateKey` symmetricKey : The key to sign and encrypt the SOAP envelope
+ - `crypto:PublicKey` servicePublicKey : The key to encrypt the symmetric key
+ - `SignatureAlgorithm` signatureAlgorithm : The algorithm to sign the SOAP envelope
+ - `EncryptionAlgorithm` encryptionAlgorithm : The algorithm to encrypt the SOAP envelope
+ - `string` x509Token : The path or token of the X509 certificate
+
+- `AsymmetricBindingConfig`: Represents the record for Username Token with Asymmetric Binding policy.
+ - Fields:
+ - `crypto:PrivateKey` signatureKey : The private key to sign the SOAP envelope
+ - `crypto:PublicKey` encryptionKey : The public key to encrypt the SOAP body
+ - `SignatureAlgorithm` signatureAlgorithm : The algorithm to sign the SOAP envelope
+ - `EncryptionAlgorithm` encryptionAlgorithm : The algorithm to encrypt the SOAP body
+ - `string` x509Token : field description
+
+#### Outbound Security Configurations
+
+- `OutboundSecurityConfig`: Represents the record for outbound security configurations to verify and decrypt SOAP envelopes.
+ - Fields:
+ - `crypto:PublicKey` verificationKey : The public key to verify the signature of the SOAP envelope
+ - `crypto:PrivateKey`|`crypto:PublicKey` decryptionKey : The private key to decrypt the SOAP envelope
+ - `SignatureAlgorithm` signatureAlgorithm : The algorithm to verify the SOAP envelope
+ - `EncryptionAlgorithm` decryptionAlgorithm : The algorithm to decrypt the SOAP body
+
+### Apply Security Policies
+
+#### SOAP 1.1 Client: UsernameToken and TranportBinding Policy
+
+```ballerina
+import ballerina/crypto;
+import ballerina/mime;
+import ballerina/soap;
+import ballerina/soap:soap11;
+
+public function main() returns error? {
+ soap11:Client soapClient = check new ("http://www.dneonline.com/calculator.asmx?WSDL",
+ {
+ inboundSecurity: [
+ {
+ username: "username",
+ password: "password",
+ passwordType: soap:TEXT
+ },
+ TRANSPORT_BINDING
+ ]
+ });
+
+ xml envelope = xml `
+
+
+ 2
+ 3
+
+
+ `;
+ xml|mime:Entity[] response = check soapClient->sendReceive(envelope, "http://tempuri.org/Add");
+}
+```
+
+#### SOAP 1.2 Client with Asymmetric Binding and Outbound Security Configuration
+
+```ballerina
+import ballerina/crypto;
+import ballerina/mime;
+import ballerina/soap;
+import ballerina/soap:soap12;
+
+public function main() returns error? {
+ configurable crypto:PrivateKey clientPrivateKey = ?;
+ configurable crypto:PublicKey clientPublicKey = ?;
+ configurable crypto:PublicKey serverPublicKey = ?;
+
+ soap12:Client soapClient = check new ("http://www.dneonline.com/calculator.asmx?WSDL",
+ {
+ inboundSecurity: {
+ signatureAlgorithm: soap:RSA_SHA256,
+ encryptionAlgorithm: soap:RSA_ECB,
+ signatureKey: clientPrivateKey,
+ encryptionKey: serverPublicKey,
+ },
+ outboundSecurity: {
+ verificationKey: serverPublicKey,
+ signatureAlgorithm: soap:RSA_SHA256,
+ decryptionKey: clientPrivateKey,
+ decryptionAlgorithm: soap:RSA_ECB
+ }
+ });
+
+ xml envelope = xml `
+
+
+ 2
+ 3
+
+
+ `;
+ xml|mime:Entity[] response = check soapClient->sendReceive(envelope, "http://tempuri.org/Add");
+}
+```
## Issues and projects
diff --git a/ballerina/Ballerina.toml b/ballerina/Ballerina.toml
index 6af2b70..2b70fd9 100644
--- a/ballerina/Ballerina.toml
+++ b/ballerina/Ballerina.toml
@@ -3,8 +3,39 @@ org = "ballerina"
name = "soap"
version = "0.2.0"
authors = ["Ballerina"]
+export=["soap", "soap.soap11", "soap.soap12"]
keywords = ["soap"]
repository = "https://github.com/ballerina-platform/module-ballerina-soap"
icon = "icon.png"
license = ["Apache-2.0"]
distribution = "2201.8.0"
+
+[build-options]
+observabilityIncluded = true
+
+[platform.java17]
+graalvmCompatible = true
+
+[[platform.java17.dependency]]
+groupId = "io.ballerina.stdlib"
+artifactId = "soap-native"
+version = "0.2.0"
+path = "../native/build/libs/soap-native-0.2.0-SNAPSHOT.jar"
+
+[[platform.java17.dependency]]
+groupId = "org.apache.wss4j"
+artifactId = "wss4j-ws-security-dom"
+version = "3.0.1"
+path = "./lib/wss4j-ws-security-dom-3.0.1.jar"
+
+[[platform.java17.dependency]]
+groupId = "org.apache.wss4j"
+artifactId = "wss4j-ws-security-common"
+version = "3.0.1"
+path = "./lib/wss4j-ws-security-common-3.0.1.jar"
+
+[[platform.java17.dependency]]
+groupId = "org.apache.santuario"
+artifactId = "xmlsec"
+version = "3.0.2"
+path = "./lib/xmlsec-3.0.2.jar"
diff --git a/ballerina/Dependencies.toml b/ballerina/Dependencies.toml
index 4903bb7..7b1c154 100644
--- a/ballerina/Dependencies.toml
+++ b/ballerina/Dependencies.toml
@@ -5,7 +5,7 @@
[ballerina]
dependencies-toml-version = "2"
-distribution-version = "2201.8.0-20230726-145300-b2bdf796"
+distribution-version = "2201.8.0"
[[package]]
org = "ballerina"
@@ -22,7 +22,7 @@ dependencies = [
[[package]]
org = "ballerina"
name = "cache"
-version = "3.7.0"
+version = "3.7.1"
dependencies = [
{org = "ballerina", name = "constraint"},
{org = "ballerina", name = "jballerina.java"},
@@ -46,6 +46,9 @@ dependencies = [
{org = "ballerina", name = "jballerina.java"},
{org = "ballerina", name = "time"}
]
+modules = [
+ {org = "ballerina", packageName = "crypto", moduleName = "crypto"}
+]
[[package]]
org = "ballerina"
@@ -61,7 +64,7 @@ dependencies = [
[[package]]
org = "ballerina"
name = "http"
-version = "2.10.0"
+version = "2.10.3"
dependencies = [
{org = "ballerina", name = "auth"},
{org = "ballerina", name = "cache"},
@@ -98,11 +101,17 @@ 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"
@@ -148,7 +157,6 @@ dependencies = [
org = "ballerina"
name = "lang.error"
version = "0.0.0"
-scope = "testOnly"
dependencies = [
{org = "ballerina", name = "jballerina.java"}
]
@@ -175,6 +183,9 @@ version = "0.0.0"
dependencies = [
{org = "ballerina", name = "jballerina.java"}
]
+modules = [
+ {org = "ballerina", packageName = "lang.regexp", moduleName = "lang.regexp"}
+]
[[package]]
org = "ballerina"
@@ -260,12 +271,20 @@ org = "ballerina"
name = "soap"
version = "0.2.0"
dependencies = [
+ {org = "ballerina", name = "crypto"},
{org = "ballerina", name = "http"},
+ {org = "ballerina", name = "io"},
+ {org = "ballerina", name = "jballerina.java"},
+ {org = "ballerina", name = "lang.regexp"},
{org = "ballerina", name = "mime"},
- {org = "ballerina", name = "test"}
+ {org = "ballerina", name = "test"},
+ {org = "ballerinai", name = "observe"}
]
modules = [
- {org = "ballerina", packageName = "soap", moduleName = "soap"}
+ {org = "ballerina", packageName = "soap", moduleName = "soap"},
+ {org = "ballerina", packageName = "soap", moduleName = "soap.soap11"},
+ {org = "ballerina", packageName = "soap", moduleName = "soap.soap12"},
+ {org = "ballerina", packageName = "soap", moduleName = "soap.wssec"}
]
[[package]]
@@ -281,7 +300,6 @@ dependencies = [
org = "ballerina"
name = "test"
version = "0.0.0"
-scope = "testOnly"
dependencies = [
{org = "ballerina", name = "jballerina.java"},
{org = "ballerina", name = "lang.error"}
@@ -306,3 +324,15 @@ dependencies = [
{org = "ballerina", name = "jballerina.java"}
]
+[[package]]
+org = "ballerinai"
+name = "observe"
+version = "0.0.0"
+dependencies = [
+ {org = "ballerina", name = "jballerina.java"},
+ {org = "ballerina", name = "observe"}
+]
+modules = [
+ {org = "ballerinai", packageName = "observe", moduleName = "observe"}
+]
+
diff --git a/ballerina/Module.md b/ballerina/Module.md
index a096113..5d724bc 100644
--- a/ballerina/Module.md
+++ b/ballerina/Module.md
@@ -1,7 +1,216 @@
## Overview
-This module provides APIs to send an ordinary XML request to a SOAP backend by specifying the necessary details to construct a SOAP envelope.
+This module offers a set of APIs that facilitate the transmission of XML requests to a SOAP backend. It excels in managing security policies within SOAP requests, ensuring the transmission of secured SOAP envelopes. Moreover, it possesses the capability to efficiently extract data from security-applied SOAP responses.
-Soap module abstracts out the details of the creation of a SOAP envelope, headers, and the body in a SOAP message.
+SOAP module abstracts out the details of the creation of a SOAP envelope, headers, and the body in a SOAP message.
-For information on the operations, which you can perform with the `soap` module, see the below **Functions**.
+## Client
+
+The `Client` is used to connect to and interact with `SOAP` endpoints.
+
+### SOAP 1.1 Client
+
+```ballerina
+import ballerina/soap:soap11;
+
+soap11:Client soapClient = check new ("http://www.dneonline.com/calculator.asmx?WSDL");
+```
+
+### SOAP 1.2 Client
+
+```ballerina
+import ballerina/soap:soap12;
+
+soap12:Client soapClient = check new ("http://www.dneonline.com/calculator.asmx?WSDL");
+```
+
+## APIs associated with SOAP
+
+- **Send & Receive**: Sends SOAP request and receives a response.
+- **Send Only**: Fires and forgets requests. Sends the request without the possibility of any response from the service.
+
+The SOAP 1.1 specification requires the inclusion of the `action` parameter as a mandatory component within its APIs. In contrast, SOAP 1.2 relaxes this requirement, making the action parameter optional.
+
+### Example: Send & Receive
+
+```ballerina
+import ballerina/soap:soap11;
+
+public function main() returns error? {
+ soap11:Client soapClient = check new ("http://www.dneonline.com/calculator.asmx?WSDL");
+
+ xml envelope = xml `
+
+
+ 2
+ 3
+
+
+ `;
+ xml|mime:Entity[] response = check soapClient->sendReceive(envelope, "http://tempuri.org/Add");
+}
+```
+
+### Example: Send Only
+
+```ballerina
+import ballerina/soap:soap11;
+
+public function main() returns error? {
+ soap11:Client soapClient = check new ("http://www.dneonline.com/calculator.asmx?WSDL");
+
+ xml envelope = xml `
+
+
+ 2
+ 3
+
+
+ `;
+ check soapClient->sendOnly(envelope, "http://tempuri.org/Add");
+}
+```
+## Security
+
+The SOAP client module introduces a robust framework for configuring security measures in SOAP communication. Security is a critical concern when exchanging data via web services, and this module offers comprehensive options to fortify SOAP requests and responses.
+
+There are two primary security configurations available for SOAP clients:
+
+- `inboundSecurity`: This configuration is applied to the SOAP envelope when a request is made. It includes various ws security policies such as Username Token, Timestamp Token, X509 Token, Symmetric Binding, Asymmetric Binding, and Transport Binding, either individually or in combination with each other.
+
+- `outboundSecurity`: This configuration is applied to the SOAP envelope when a response is received. Its purpose is to decrypt the data within the envelope and verify the digital signature for security validation.
+
+### Policies
+
+This library currently supports the following WS Security policies:
+
+- **Username Token**: Provides authentication through username and password credentials.
+- **Timestamp Token**: Enhances message integrity by incorporating timestamp information.
+- **X509 Token**: Allows the use of X.509 certificates for secure communication.
+- **Symmetric Binding**: Enables symmetric key-based security mechanisms.
+- **Asymmetric Binding**: Facilitates the use of asymmetric cryptography for enhanced security.
+
+These policies empower SOAP clients to enhance the security of their web service communications by selecting and implementing the appropriate security mechanisms to safeguard their SOAP envelopes.
+
+### Security Policy Configuration Types
+
+#### Inbound Security Configurations
+
+- `TimestampTokenConfig`: Represents the record for Timestamp Token policy.
+ - Fields:
+ - `int` timeToLive : The time to get expired
+
+- `UsernameTokenConfig`: Represents the record for Username Token policy.
+ - Fields:
+ - `string` username : The name of the user
+ - `string` password : The password of the user
+ - `PasswordType` passwordType : The password type of the username token
+
+- `SymmetricBindingConfig`: Represents the record for Symmetric Binding policy.
+ - Fields:
+ - `crypto:PrivateKey` symmetricKey : The key to sign and encrypt the SOAP envelope
+ - `crypto:PublicKey` servicePublicKey : The key to encrypt the symmetric key
+ - `SignatureAlgorithm` signatureAlgorithm : The algorithm to sign the SOAP envelope
+ - `EncryptionAlgorithm` encryptionAlgorithm : The algorithm to encrypt the SOAP envelope
+ - `string` x509Token : The path or token of the X509 certificate
+
+- `AsymmetricBindingConfig`: Represents the record for Username Token with Asymmetric Binding policy.
+ - Fields:
+ - `crypto:PrivateKey` signatureKey : The private key to sign the SOAP envelope
+ - `crypto:PublicKey` encryptionKey : The public key to encrypt the SOAP body
+ - `SignatureAlgorithm` signatureAlgorithm : The algorithm to sign the SOAP envelope
+ - `EncryptionAlgorithm` encryptionAlgorithm : The algorithm to encrypt the SOAP body
+ - `string` x509Token : field description
+
+#### Outbound Security Configurations
+
+- `OutboundSecurityConfig`: Represents the record for outbound security configurations to verify and decrypt SOAP envelopes.
+ - Fields:
+ - `crypto:PublicKey` verificationKey : The public key to verify the signature of the SOAP envelope
+ - `crypto:PrivateKey`|`crypto:PublicKey` decryptionKey : The private key to decrypt the SOAP envelope
+ - `SignatureAlgorithm` signatureAlgorithm : The algorithm to verify the SOAP envelope
+ - `EncryptionAlgorithm` decryptionAlgorithm : The algorithm to decrypt the SOAP body
+
+### Apply Security Policies
+
+#### SOAP 1.1 Client: UsernameToken and TranportBinding Policy
+
+```ballerina
+import ballerina/crypto;
+import ballerina/mime;
+import ballerina/soap;
+import ballerina/soap:soap11;
+
+public function main() returns error? {
+ soap11:Client soapClient = check new ("http://www.dneonline.com/calculator.asmx?WSDL",
+ {
+ inboundSecurity: [
+ {
+ username: "username",
+ password: "password",
+ passwordType: soap:TEXT
+ },
+ TRANSPORT_BINDING
+ ]
+ });
+
+ xml envelope = xml `
+
+
+ 2
+ 3
+
+
+ `;
+ xml|mime:Entity[] response = check soapClient->sendReceive(envelope, "http://tempuri.org/Add");
+}
+```
+
+#### SOAP 1.2 Client with Asymmetric Binding and Outbound Security Configuration
+
+```ballerina
+import ballerina/crypto;
+import ballerina/mime;
+import ballerina/soap;
+import ballerina/soap:soap12;
+
+public function main() returns error? {
+ configurable crypto:PrivateKey clientPrivateKey = ?;
+ configurable crypto:PublicKey clientPublicKey = ?;
+ configurable crypto:PublicKey serverPublicKey = ?;
+
+ soap12:Client soapClient = check new ("http://www.dneonline.com/calculator.asmx?WSDL",
+ {
+ inboundSecurity: {
+ signatureAlgorithm: soap:RSA_SHA256,
+ encryptionAlgorithm: soap:RSA_ECB,
+ signatureKey: clientPrivateKey,
+ encryptionKey: serverPublicKey,
+ },
+ outboundSecurity: {
+ verificationKey: serverPublicKey,
+ signatureAlgorithm: soap:RSA_SHA256,
+ decryptionKey: clientPrivateKey,
+ decryptionAlgorithm: soap:RSA_ECB
+ }
+ });
+ xml envelope = xml `
+
+
+ 2
+ 3
+
+
+ `;
+ xml|mime:Entity[] response = check soapClient->sendReceive(envelope, "http://tempuri.org/Add");
+}
+```
+
+## Report issues
+
+To report bugs, request new features, start new discussions, view project boards, etc., go to the [Ballerina standard library parent repository](https://github.com/ballerina-platform/ballerina-standard-library).
+
+## Useful links
+
+- Chat live with us via our [Discord server](https://discord.gg/ballerinalang).
+- Post all technical questions on Stack Overflow with the [#ballerina](https://stackoverflow.com/questions/tagged/ballerina) tag.
diff --git a/ballerina/Package.md b/ballerina/Package.md
index 1a43586..f5db035 100644
--- a/ballerina/Package.md
+++ b/ballerina/Package.md
@@ -1,8 +1,201 @@
## Package overview
-This module provides APIs to send an ordinary XML request to a SOAP backend by specifying the necessary details to construct a SOAP envelope.
+This module offers a set of APIs that facilitate the transmission of XML requests to a SOAP backend. It excels in managing security policies within SOAP requests, ensuring the transmission of secured SOAP envelopes. Moreover, it possesses the capability to efficiently extract data from security-applied SOAP responses.
-Soap module abstracts out the details of the creation of a SOAP envelope, headers, and the body in a SOAP message.
+SOAP module abstracts out the details of the creation of a SOAP envelope, headers, and the body in a SOAP message.
+
+## Client
+
+The `Client` is used to connect to and interact with `SOAP` endpoints.
+
+### SOAP 1.1 Client
+
+```ballerina
+import ballerina/soap:soap11;
+
+soap11:Client soapClient = check new ("http://www.dneonline.com/calculator.asmx?WSDL");
+```
+
+### SOAP 1.2 Client
+
+```ballerina
+import ballerina/soap:soap12;
+
+soap12:Client soapClient = check new ("http://www.dneonline.com/calculator.asmx?WSDL");
+```
+
+## APIs associated with SOAP
+
+- **Send & Receive**: Sends SOAP request and receives a response.
+- **Send Only**: Fires and forgets requests. Sends the request without the possibility of any response from the service.
+
+The SOAP 1.1 specification requires the inclusion of the `action` parameter as a mandatory component within its APIs. In contrast, SOAP 1.2 relaxes this requirement, making the action parameter optional.
+
+### Example: Send & Receive
+
+```ballerina
+import ballerina/soap:soap11;
+
+public function main() returns error? {
+ soap11:Client soapClient = check new ("http://www.dneonline.com/calculator.asmx?WSDL");
+
+ xml envelope = xml `
+
+
+ 2
+ 3
+
+
+ `;
+ xml|mime:Entity[] response = check soapClient->sendReceive(envelope, "http://tempuri.org/Add");
+}
+```
+
+### Example: Send Only
+
+```ballerina
+import ballerina/soap:soap11;
+
+public function main() returns error? {
+ soap11:Client soapClient = check new ("http://www.dneonline.com/calculator.asmx?WSDL");
+
+ xml envelope = xml `
+
+
+ 2
+ 3
+
+
+ `;
+ check soapClient->sendOnly(envelope, "http://tempuri.org/Add");
+}
+```
+## Security
+
+The SOAP client module introduces a robust framework for configuring security measures in SOAP communication. Security is a critical concern when exchanging data via web services, and this module offers comprehensive options to fortify SOAP requests and responses.
+
+There are two primary security configurations available for SOAP clients:
+
+- `inboundSecurity`: This configuration is applied to the SOAP envelope when a request is made. It includes various ws security policies such as Username Token, Timestamp Token, X509 Token, Symmetric Binding, Asymmetric Binding, and Transport Binding, either individually or in combination with each other.
+
+- `outboundSecurity`: This configuration is applied to the SOAP envelope when a response is received. Its purpose is to decrypt the data within the envelope and verify the digital signature for security validation.
+
+### Policies
+
+This library currently supports the following WS Security policies:
+
+- **Username Token**: Provides authentication through username and password credentials.
+- **Timestamp Token**: Enhances message integrity by incorporating timestamp information.
+- **X509 Token**: Allows the use of X.509 certificates for secure communication.
+- **Symmetric Binding**: Enables symmetric key-based security mechanisms.
+- **Asymmetric Binding**: Facilitates the use of asymmetric cryptography for enhanced security.
+
+These policies empower SOAP clients to enhance the security of their web service communications by selecting and implementing the appropriate security mechanisms to safeguard their SOAP envelopes.
+
+### Security Policy Configuration Types
+
+#### Inbound Security Configurations
+
+- `TimestampTokenConfig`: Represents the record for Timestamp Token policy.
+ - Fields:
+ - `int` timeToLive : The time to get expired
+
+- `UsernameTokenConfig`: Represents the record for Username Token policy.
+ - Fields:
+ - `string` username : The name of the user
+ - `string` password : The password of the user
+ - `PasswordType` passwordType : The password type of the username token
+
+- `SymmetricBindingConfig`: Represents the record for Symmetric Binding policy.
+ - Fields:
+ - `crypto:PrivateKey` symmetricKey : The key to sign and encrypt the SOAP envelope
+ - `crypto:PublicKey` servicePublicKey : The key to encrypt the symmetric key
+ - `SignatureAlgorithm` signatureAlgorithm : The algorithm to sign the SOAP envelope
+ - `EncryptionAlgorithm` encryptionAlgorithm : The algorithm to encrypt the SOAP envelope
+ - `string` x509Token : The path or token of the X509 certificate
+
+- `AsymmetricBindingConfig`: Represents the record for Username Token with Asymmetric Binding policy.
+ - Fields:
+ - `crypto:PrivateKey` signatureKey : The private key to sign the SOAP envelope
+ - `crypto:PublicKey` encryptionKey : The public key to encrypt the SOAP body
+ - `SignatureAlgorithm` signatureAlgorithm : The algorithm to sign the SOAP envelope
+ - `EncryptionAlgorithm` encryptionAlgorithm : The algorithm to encrypt the SOAP body
+ - `string` x509Token : field description
+
+#### Outbound Security Configurations
+
+- `OutboundSecurityConfig`: Represents the record for outbound security configurations to verify and decrypt SOAP envelopes.
+ - Fields:
+ - `crypto:PublicKey` verificationKey : The public key to verify the signature of the SOAP envelope
+ - `crypto:PrivateKey`|`crypto:PublicKey` decryptionKey : The private key to decrypt the SOAP envelope
+ - `SignatureAlgorithm` signatureAlgorithm : The algorithm to verify the SOAP envelope
+ - `EncryptionAlgorithm` decryptionAlgorithm : The algorithm to decrypt the SOAP body
+
+### Apply Security Policies
+
+#### SOAP 1.1 Client: UsernameToken and TranportBinding Policy
+
+```
+import ballerina/crypto;
+import ballerina/mime;
+import ballerina/soap;
+import ballerina/soap:soap11;
+
+public function main() returns error? {
+ soap11:Client soapClient = check new ("http://www.dneonline.com/calculator.asmx?WSDL",
+ {
+ inboundSecurity: [
+ {
+ username: "username",
+ password: "password",
+ passwordType: soap:TEXT
+ },
+ TRANSPORT_BINDING
+ ]
+ });
+
+ xml envelope = xml `
+
+
+ 2
+ 3
+
+
+ `;
+ xml|mime:Entity[] response = check soapClient->sendReceive(envelope, "http://tempuri.org/Add");
+}
+```
+
+#### SOAP 1.2 Client with Asymmetric Binding and Outbound Security Configuration
+
+```ballerina
+import ballerina/crypto;
+import ballerina/mime;
+import ballerina/soap;
+import ballerina/soap:soap12;
+
+public function main() returns error? {
+ configurable crypto:PrivateKey clientPrivateKey = ?;
+ configurable crypto:PublicKey clientPublicKey = ?;
+ configurable crypto:PublicKey serverPublicKey = ?;
+
+ soap12:Client soapClient = check new ("http://www.dneonline.com/calculator.asmx?WSDL",
+ {
+ inboundSecurity: {
+ signatureAlgorithm: soap:RSA_SHA256,
+ encryptionAlgorithm: soap:RSA_ECB,
+ signatureKey: clientPrivateKey,
+ encryptionKey: serverPublicKey,
+ },
+ outboundSecurity: {
+ verificationKey: serverPublicKey,
+ signatureAlgorithm: soap:RSA_SHA256,
+ decryptionKey: clientPrivateKey,
+ decryptionAlgorithm: soap:RSA_ECB
+ }
+ });
+}
+```
## Report issues
diff --git a/ballerina/build.gradle b/ballerina/build.gradle
index 2b64e91..c112a14 100644
--- a/ballerina/build.gradle
+++ b/ballerina/build.gradle
@@ -32,7 +32,7 @@ buildscript {
}
-description = 'Ballerina - SOAP module'
+description = 'Ballerina - SOAP Module'
def packageName = "soap"
def packageOrg = "ballerina"
@@ -106,6 +106,8 @@ publishing {
updateTomlFiles.dependsOn copyStdlibs
+test.dependsOn ":${packageName}-native:build"
build.dependsOn "generatePomFileForMavenPublication"
+build.dependsOn ":${packageName}-native:build"
publishToMavenLocal.dependsOn build
publish.dependsOn build
diff --git a/ballerina/configs.bal b/ballerina/configs.bal
new file mode 100644
index 0000000..22bc461
--- /dev/null
+++ b/ballerina/configs.bal
@@ -0,0 +1,30 @@
+// Copyright (c) 2023, WSO2 LLC. (http://www.wso2.com) All Rights Reserved.
+//
+// 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 soap.wssec;
+
+import ballerina/http;
+
+# SOAP client configurations.
+#
+# + httpConfig - HTTP Configuration
+# + inboundSecurity - Web service security configurations for SOAP requests
+# + outboundSecurity - Web service security configurations to decrypt and verify SOAP responses
+public type ClientConfig record {|
+ http:ClientConfiguration httpConfig = {};
+ wssec:InboundSecurityConfig|wssec:InboundSecurityConfig[] inboundSecurity = NO_POLICY;
+ wssec:OutboundSecurityConfig outboundSecurity = {};
+|};
diff --git a/ballerina/constants.bal b/ballerina/constants.bal
new file mode 100644
index 0000000..8c846c7
--- /dev/null
+++ b/ballerina/constants.bal
@@ -0,0 +1,21 @@
+// Copyright (c) 2023, WSO2 LLC. (http://www.wso2.com) All Rights Reserved.
+//
+// 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 soap.wssec;
+
+const SOAP_ACTION = "SOAPAction";
+const ACTION = "action";
+const wssec:NoPolicy NO_POLICY = "NoPolicy";
diff --git a/ballerina/error.bal b/ballerina/error.bal
index 3795222..9fb3440 100644
--- a/ballerina/error.bal
+++ b/ballerina/error.bal
@@ -1,4 +1,4 @@
-// Copyright (c) 2023, WSO2 LLC. (http://www.wso2.org) All Rights Reserved.
+// Copyright (c) 2023, WSO2 LLC. (http://www.wso2.com) All Rights Reserved.
//
// WSO2 LLC. licenses this file to you under the Apache License,
// Version 2.0 (the "License"); you may not use this file except
@@ -16,3 +16,6 @@
# Defines the common error type for the module.
public type Error distinct error;
+
+const SOAP_RESPONSE_ERROR = "Failed to create SOAP response.";
+const INVALID_PROTOCOL_ERROR = "Invalid protocol detected: Please use the `https` protocol instead of `http`.";
diff --git a/ballerina/modules/soap11/Module.md b/ballerina/modules/soap11/Module.md
new file mode 100644
index 0000000..ea56f06
--- /dev/null
+++ b/ballerina/modules/soap11/Module.md
@@ -0,0 +1,166 @@
+## Overview
+
+This module offers a set of APIs that facilitate the transmission of XML requests to a SOAP 1.1 backend. It excels in managing security policies within SOAP requests, ensuring the transmission of secured SOAP envelopes. Moreover, it possesses the capability to efficiently extract data from security-applied SOAP responses.
+
+SOAP module abstracts out the details of the creation of a SOAP envelope, headers, and the body in a SOAP message.
+
+## Client
+
+The `Client` is used to connect to and interact with `SOAP` 1.1 endpoints.
+
+### SOAP 1.1 Client
+
+```ballerina
+import ballerina/soap:soap11;
+
+soap11:Client soapClient = check new ("http://www.dneonline.com/calculator.asmx?WSDL");
+```
+
+## APIs associated with SOAP
+
+- **Send & Receive**: Sends SOAP request and receives a response.
+- **Send Only**: Fires and forgets requests. Sends the request without the possibility of any response from the service.
+
+The SOAP 1.1 specification requires the inclusion of the `action` parameter as a mandatory component within its APIs. In contrast, SOAP 1.2 relaxes this requirement, making the action parameter optional.
+
+### Example: Send & Receive
+
+```ballerina
+import ballerina/soap:soap11;
+
+public function main() returns error? {
+ soap11:Client soapClient = check new ("http://www.dneonline.com/calculator.asmx?WSDL");
+
+ xml envelope = xml `
+
+
+ 2
+ 3
+
+
+ `;
+ xml|mime:Entity[] response = check soapClient->sendReceive(envelope, "http://tempuri.org/Add");
+}
+```
+
+### Example: Send Only
+
+```ballerina
+import ballerina/soap:soap11;
+
+public function main() returns error? {
+ soap11:Client soapClient = check new ("http://www.dneonline.com/calculator.asmx?WSDL");
+
+ xml envelope = xml `
+
+
+ 2
+ 3
+
+
+ `;
+ check soapClient->sendOnly(envelope, "http://tempuri.org/Add");
+}
+```
+## Security
+
+The SOAP client module introduces a robust framework for configuring security measures in SOAP communication. Security is a critical concern when exchanging data via web services, and this module offers comprehensive options to fortify SOAP requests and responses.
+
+There are two primary security configurations available for SOAP clients:
+
+- `inboundSecurity`: This configuration is applied to the SOAP envelope when a request is made. It includes various ws security policies such as Username Token, Timestamp Token, X509 Token, Symmetric Binding, Asymmetric Binding, and Transport Binding, either individually or in combination with each other.
+
+- `outboundSecurity`: This configuration is applied to the SOAP envelope when a response is received. Its purpose is to decrypt the data within the envelope and verify the digital signature for security validation.
+
+### Policies
+
+This library currently supports the following WS Security policies:
+
+- **Username Token**: Provides authentication through username and password credentials.
+- **Timestamp Token**: Enhances message integrity by incorporating timestamp information.
+- **X509 Token**: Allows the use of X.509 certificates for secure communication.
+- **Symmetric Binding**: Enables symmetric key-based security mechanisms.
+- **Asymmetric Binding**: Facilitates the use of asymmetric cryptography for enhanced security.
+
+These policies empower SOAP clients to enhance the security of their web service communications by selecting and implementing the appropriate security mechanisms to safeguard their SOAP envelopes.
+
+### Security Policy Configuration Types
+
+#### Inbound Security Configurations
+
+- `TimestampTokenConfig`: Represents the record for Timestamp Token policy.
+ - Fields:
+ - `int` timeToLive : The time to get expired
+
+- `UsernameTokenConfig`: Represents the record for Username Token policy.
+ - Fields:
+ - `string` username : The name of the user
+ - `string` password : The password of the user
+ - `PasswordType` passwordType : The password type of the username token
+
+- `SymmetricBindingConfig`: Represents the record for Symmetric Binding policy.
+ - Fields:
+ - `crypto:PrivateKey` symmetricKey : The key to sign and encrypt the SOAP envelope
+ - `crypto:PublicKey` servicePublicKey : The key to encrypt the symmetric key
+ - `SignatureAlgorithm` signatureAlgorithm : The algorithm to sign the SOAP envelope
+ - `EncryptionAlgorithm` encryptionAlgorithm : The algorithm to encrypt the SOAP envelope
+ - `string` x509Token : The path or token of the X509 certificate
+
+- `AsymmetricBindingConfig`: Represents the record for Username Token with Asymmetric Binding policy.
+ - Fields:
+ - `crypto:PrivateKey` signatureKey : The private key to sign the SOAP envelope
+ - `crypto:PublicKey` encryptionKey : The public key to encrypt the SOAP body
+ - `SignatureAlgorithm` signatureAlgorithm : The algorithm to sign the SOAP envelope
+ - `EncryptionAlgorithm` encryptionAlgorithm : The algorithm to encrypt the SOAP body
+ - `string` x509Token : field description
+
+#### Outbound Security Configurations
+
+- `OutboundSecurityConfig`: Represents the record for outbound security configurations to verify and decrypt SOAP envelopes.
+ - Fields:
+ - `crypto:PublicKey` verificationKey : The public key to verify the signature of the SOAP envelope
+ - `crypto:PrivateKey`|`crypto:PublicKey` decryptionKey : The private key to decrypt the SOAP envelope
+ - `SignatureAlgorithm` signatureAlgorithm : The algorithm to verify the SOAP envelope
+ - `EncryptionAlgorithm` decryptionAlgorithm : The algorithm to decrypt the SOAP body
+
+### Apply Security Policies
+
+#### SOAP 1.1 Client with Asymmetric Binding and Outbound Security Configuration
+
+```ballerina
+import ballerina/crypto;
+import ballerina/mime;
+import ballerina/soap;
+import ballerina/soap:soap11;
+
+public function main() returns error? {
+ crypto:PrivateKey clientPrivateKey = ...//
+ crypto:PublicKey clientPublicKey = ...//
+ crypto:PublicKey serverPublicKey = ...//
+
+ soap11:Client soapClient = check new ("http://www.dneonline.com/calculator.asmx?WSDL",
+ {
+ inboundSecurity: {
+ signatureAlgorithm: soap:RSA_SHA256,
+ encryptionAlgorithm: soap:RSA_ECB,
+ signatureKey: clientPrivateKey,
+ encryptionKey: serverPublicKey,
+ },
+ outboundSecurity: {
+ verificationKey: serverPublicKey,
+ signatureAlgorithm: soap:RSA_SHA256,
+ decryptionKey: clientPrivateKey,
+ decryptionAlgorithm: soap:RSA_ECB
+ }
+ });
+}
+```
+
+## Report issues
+
+To report bugs, request new features, start new discussions, view project boards, etc., go to the [Ballerina standard library parent repository](https://github.com/ballerina-platform/ballerina-standard-library).
+
+## Useful links
+
+- Chat live with us via our [Discord server](https://discord.gg/ballerinalang).
+- Post all technical questions on Stack Overflow with the [#ballerina](https://stackoverflow.com/questions/tagged/ballerina) tag.
diff --git a/ballerina/modules/soap11/error.bal b/ballerina/modules/soap11/error.bal
new file mode 100644
index 0000000..50d5fe4
--- /dev/null
+++ b/ballerina/modules/soap11/error.bal
@@ -0,0 +1,23 @@
+// Copyright (c) 2023, WSO2 LLC. (http://www.wso2.com) All Rights Reserved.
+//
+// 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.
+
+# Defines the common error type for the SOAP 1.1 module.
+public type Error distinct error;
+
+const SOAP_RESPONSE_ERROR = "Failed to create SOAP response.";
+const SOAP_CLIENT_ERROR = "Failed to initialize SOAP 1.1 client.";
+const SOAP_ERROR = "Error occurred while executing the API";
+const INVALID_OUTBOUND_SECURITY_ERROR = "Outbound security configurations do not match with the SOAP response.";
diff --git a/ballerina/modules/soap11/soap11.bal b/ballerina/modules/soap11/soap11.bal
new file mode 100644
index 0000000..33ea2ee
--- /dev/null
+++ b/ballerina/modules/soap11/soap11.bal
@@ -0,0 +1,114 @@
+// Copyright (c) 2023, WSO2 LLC. (http://www.wso2.com) All Rights Reserved.
+//
+// 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 soap;
+import soap.wssec;
+
+import ballerina/http;
+import ballerina/mime;
+
+# Object for the basic SOAP client endpoint.
+public isolated client class Client {
+ private final http:Client soapClient;
+ private final readonly & wssec:InboundSecurityConfig|wssec:InboundSecurityConfig[] inboundSecurity;
+ private final readonly & wssec:OutboundSecurityConfig outboundSecurity;
+
+ # Gets invoked during object initialization.
+ #
+ # + url - URL endpoint
+ # + config - Configurations for SOAP client
+ # + return - `error` in case of errors or `()` otherwise
+ public isolated function init(string url, *soap:ClientConfig config) returns Error? {
+ do {
+ check soap:validateTransportBindingPolicy(config);
+ self.soapClient = check new (url, config.httpConfig);
+ readonly & soap:ClientConfig readonlyConfig = soap:getReadOnlyClientConfig(config);
+ self.inboundSecurity = readonlyConfig.inboundSecurity;
+ self.outboundSecurity = readonlyConfig.outboundSecurity;
+ } on fail var err {
+ return error Error(SOAP_CLIENT_ERROR, err);
+ }
+ }
+
+ # Sends SOAP request and expects a response.
+ # ```ballerina
+ # xml|mime:Entity[] response = check soapClient->sendReceive(body, action);
+ # ```
+ #
+ # + body - SOAP request body as an `XML` or `mime:Entity[]` to work with SOAP attachments
+ # + action - SOAP action as a `string`
+ # + headers - SOAP headers as a `map`
+ # + path - The resource path
+ # + return - If successful, returns the response. Else, returns an error
+ remote isolated function sendReceive(xml|mime:Entity[] body, string action,
+ map headers = {}, string path = "")
+ returns xml|mime:Entity[]|Error {
+ do {
+ xml securedBody;
+ xml mimeEntity = body is xml ? body : check body[0].getXml();
+ lock {
+ xml envelope = body is xml ? body.clone() : mimeEntity.clone();
+ securedBody = check soap:applySecurityPolicies(self.inboundSecurity.clone(), envelope.clone());
+ }
+ xml response;
+ if body is mime:Entity[] {
+ body[0].setXml(securedBody);
+ response = check soap:sendReceive(body, self.soapClient, action, headers, path, false);
+ } else {
+ response = check soap:sendReceive(securedBody, self.soapClient, action, headers, path, false);
+ }
+ lock {
+ wssec:OutboundSecurityConfig? outboundSecurity = self.outboundSecurity.clone();
+ do {
+ if outboundSecurity is wssec:OutboundSecurityConfig {
+ return check soap:applyOutboundConfig(outboundSecurity.clone(), response.clone());
+ }
+ } on fail var e {
+ return error Error(INVALID_OUTBOUND_SECURITY_ERROR, e.cause());
+ }
+ return response.clone();
+ }
+ } on fail var e {
+ return error Error(SOAP_ERROR, e.cause());
+ }
+ }
+
+ # Fires and forgets requests. Sends the request without the possibility of any response from the
+ # service (even an error).
+ # ```ballerina
+ # check soapClient->sendOnly(body, action);
+ # ```
+ #
+ # + body - SOAP request body as an `XML` or `mime:Entity[]` to work with SOAP attachments
+ # + action - SOAP action as a `string`
+ # + headers - SOAP headers as a `map`
+ # + path - The resource path
+ # + return - If successful, returns `nil`. Else, returns an error
+ remote isolated function sendOnly(xml|mime:Entity[] body, string action,
+ map headers = {}, string path = "") returns Error? {
+ do {
+ xml securedBody;
+ xml mimeEntity = body is xml ? body : check body[0].getXml();
+ lock {
+ xml envelope = body is xml ? body.clone() : mimeEntity.clone();
+ securedBody = check soap:applySecurityPolicies(self.inboundSecurity.clone(), envelope.clone());
+ }
+ return check soap:sendOnly(securedBody, self.soapClient, action, headers, path, false);
+ } on fail var e {
+ return error Error(SOAP_ERROR, e.cause());
+ }
+ }
+}
diff --git a/ballerina/modules/soap11/tests/http_soap_service.bal b/ballerina/modules/soap11/tests/http_soap_service.bal
new file mode 100644
index 0000000..75c69d5
--- /dev/null
+++ b/ballerina/modules/soap11/tests/http_soap_service.bal
@@ -0,0 +1,64 @@
+// Copyright (c) 2023, WSO2 LLC. (http://www.wso2.org) All Rights Reserved.
+//
+// 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/crypto;
+import ballerina/http;
+import ballerina/soap;
+
+const crypto:KeyStore serverKeyStore = {
+ path: X509_KEY_STORE_PATH,
+ password: KEY_PASSWORD
+};
+crypto:PrivateKey serverPrivateKey = check crypto:decodeRsaPrivateKeyFromKeyStore(serverKeyStore, KEY_ALIAS,
+ KEY_PASSWORD);
+crypto:PublicKey serverPublicKey = check crypto:decodeRsaPublicKeyFromTrustStore(serverKeyStore, KEY_ALIAS);
+
+service / on new http:Listener(9090) {
+
+ resource function post getPayload(http:Request request) returns http:Response|error {
+ http:Response response = new;
+ response.setPayload(check (check request.getBodyParts())[0].getXml());
+ return response;
+ }
+
+ resource function post getSamePayload(http:Request request) returns http:Response|error {
+ xml payload = check request.getXmlPayload();
+ http:Response response = new;
+ response.setPayload(payload);
+ return response;
+ }
+
+ resource function post getSecuredPayload(http:Request request) returns http:Response|error {
+ xml payload = check request.getXmlPayload();
+ xml applyOutboundConfig = check soap:applyOutboundConfig(
+ {
+ verificationKey: clientPublicKey,
+ signatureAlgorithm: soap:RSA_SHA256,
+ decryptionAlgorithm: soap:RSA_ECB,
+ decryptionKey: serverPrivateKey
+ }, payload);
+ xml securedEnv = check soap:applySecurityPolicies(
+ {
+ signatureAlgorithm: soap:RSA_SHA256,
+ encryptionAlgorithm: soap:RSA_ECB,
+ signatureKey: serverPrivateKey,
+ encryptionKey: clientPublicKey
+ }, applyOutboundConfig);
+ http:Response response = new;
+ response.setPayload(securedEnv);
+ return response;
+ }
+}
diff --git a/ballerina/modules/soap11/tests/soap11_client_test.bal b/ballerina/modules/soap11/tests/soap11_client_test.bal
new file mode 100644
index 0000000..37640a7
--- /dev/null
+++ b/ballerina/modules/soap11/tests/soap11_client_test.bal
@@ -0,0 +1,426 @@
+// Copyright (c) 2023, WSO2 LLC. (http://www.wso2.com) All Rights Reserved.
+//
+// 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 soap;
+import soap.wssec;
+
+import ballerina/crypto;
+import ballerina/io;
+import ballerina/mime;
+import ballerina/test;
+
+const string KEY_ALIAS = "wss40";
+const string KEY_PASSWORD = "security";
+const IMAGE_PATH = "../ballerina/icon.png";
+const FILE_PATH = "../ballerina/Module.md";
+const string KEY_STORE_PATH = "modules/wssec/tests/resources/wss40.p12";
+const string X509_KEY_STORE_PATH = "modules/wssec/tests/resources/x509_certificate.p12";
+const string X509_KEY_STORE_PATH_2 = "modules/wssec/tests/resources/x509_certificate_2.p12";
+const wssec:TransportBindingConfig TRANSPORT_BINDING = "TransportBinding";
+const wssec:NoPolicy NO_POLICY = "NoPolicy";
+
+const crypto:KeyStore clientKeyStore = {
+ path: X509_KEY_STORE_PATH_2,
+ password: KEY_PASSWORD
+};
+crypto:PrivateKey clientPrivateKey = check crypto:decodeRsaPrivateKeyFromKeyStore(clientKeyStore, KEY_ALIAS,
+ KEY_PASSWORD);
+crypto:PublicKey clientPublicKey = check crypto:decodeRsaPublicKeyFromTrustStore(clientKeyStore, KEY_ALIAS);
+
+crypto:KeyStore keyStore = {
+ path: KEY_STORE_PATH,
+ password: KEY_PASSWORD
+};
+crypto:PrivateKey symmetricKey = check crypto:decodeRsaPrivateKeyFromKeyStore(keyStore, KEY_ALIAS, KEY_PASSWORD);
+crypto:PublicKey publicKey = check crypto:decodeRsaPublicKeyFromTrustStore(keyStore, KEY_ALIAS);
+
+@test:Config {
+ groups: ["soap11", "send_receive", "mime", "aa"]
+}
+function testSendReceiveWithMime() returns error? {
+ Client soapClient = check new ("http://localhost:9090");
+ xml body = xml `
+
+
+ 2
+ 3
+
+
+ `;
+
+ mime:Entity[] mtomMessage = [];
+ mime:Entity envelope = new;
+ check envelope.setContentType("application/xop+xml");
+ envelope.setContentId("");
+ envelope.setBody(body);
+ mtomMessage.push(envelope);
+
+ mime:Entity bytesPart = new;
+ string readContent = check io:fileReadString(FILE_PATH);
+ bytesPart.setFileAsEntityBody(FILE_PATH);
+ string|byte[]|io:ReadableByteChannel|mime:EncodeError bytes = mime:base64Encode(readContent.toBytes());
+ if bytes !is byte[] {
+ return error("error");
+ }
+ bytesPart.setBody(bytes);
+ check bytesPart.setContentType("image/jpeg");
+ bytesPart.setContentId("");
+ mtomMessage.push(bytesPart);
+
+ xml|mime:Entity[] response = check soapClient->sendReceive(mtomMessage, "http://tempuri.org/Add", path = "/getPayload");
+ test:assertEquals(response, body);
+}
+
+@test:Config {
+ groups: ["soap11", "send_only"]
+}
+function testSendOnly() returns error? {
+ xml body = xml `
+
+
+ 2
+ 3
+
+
+ `;
+
+ Client soapClient = check new ("http://www.dneonline.com/calculator.asmx?WSDL");
+ check soapClient->sendOnly(body, "http://tempuri.org/Add");
+}
+
+@test:Config {
+ groups: ["soap11", "send_receive"]
+}
+function testSendReceive() returns error? {
+ Client soapClient = check new ("http://www.dneonline.com/calculator.asmx?WSDL",
+ {
+ inboundSecurity: NO_POLICY,
+ outboundSecurity: {}
+ }
+ );
+
+ xml body = xml `
+
+
+ 2
+ 3
+
+
+ `;
+ xml|mime:Entity[] response = check soapClient->sendReceive(body, "http://tempuri.org/Add");
+ xml expected = xml `5`;
+ test:assertEquals(response, expected);
+}
+
+@test:Config {
+ groups: ["soap11", "send_receive"]
+}
+function testSendReceiveWithHeaders() returns error? {
+ xml body = xml `
+
+
+ 2
+ 3
+
+
+ `;
+
+ Client soapClient = check new ("http://www.dneonline.com/calculator.asmx?WSDL");
+
+ xml|mime:Entity[] response = check soapClient->sendReceive(body, "http://tempuri.org/Add",
+ {foo: ["bar1", "bar2"]});
+ xml expected = xml `5`;
+ test:assertEquals(response, expected);
+}
+
+@test:Config {
+ groups: ["soap11"]
+}
+function testTransportBindingError() returns error? {
+ Client|Error soapClient = new ("http://www.dneonline.com/calculator.asmx?WSDL",
+ inboundSecurity = TRANSPORT_BINDING
+ );
+ test:assertTrue(soapClient is Error);
+ test:assertEquals((soapClient).message(), SOAP_CLIENT_ERROR);
+}
+
+@test:Config {
+ groups: ["soap11"]
+}
+function testTransportBindingError2() returns error? {
+ Client|Error soapClient = new ("http://www.dneonline.com/calculator.asmx?WSDL",
+ inboundSecurity = [
+ TRANSPORT_BINDING
+ ]
+ );
+ test:assertTrue(soapClient is Error);
+ test:assertEquals((soapClient).message(), SOAP_CLIENT_ERROR);
+}
+
+@test:Config {
+ groups: ["soap11", "send_receive"]
+}
+function testSendReceiveError() returns error? {
+ Client soapClient = check new ("http://www.dneonline.com/invalidcalculator.asmx?WSDL");
+ xml body = xml `
+
+
+ 2
+ 3
+
+
+ `;
+ xml|mime:Entity[]|Error response = soapClient->sendReceive(body, "http://tempuri.org/Add");
+ test:assertTrue(response is Error);
+ test:assertEquals((response).message(), SOAP_ERROR);
+}
+
+@test:Config {
+ groups: ["soap11", "send_receive", "kl"]
+}
+function testSendReceiveWithTimestampTokenSecurity() returns error? {
+ Client soapClient = check new ("http://www.dneonline.com/calculator.asmx?WSDL",
+ {
+ inboundSecurity: [
+ {
+ timeToLive: 600
+ }
+ ]
+ }
+ );
+ xml body = xml `
+
+
+ 2
+ 3
+
+
+ `;
+ xml|mime:Entity[] response = check soapClient->sendReceive(body, "http://tempuri.org/Add");
+ xml expected = xml `soap:MustUnderstandSystem.Web.Services.Protocols.SoapHeaderException: SOAP header Security was not understood.
+ at System.Web.Services.Protocols.SoapHeaderHandling.SetHeaderMembers(SoapHeaderCollection headers, Object target, SoapHeaderMapping[] mappings, SoapHeaderDirection direction, Boolean client)
+ at System.Web.Services.Protocols.SoapServerProtocol.CreateServerInstance()
+ at System.Web.Services.Protocols.WebServiceHandler.Invoke()
+ at System.Web.Services.Protocols.WebServiceHandler.CoreProcessRequest()`;
+ test:assertEquals(response.toString(), expected.toString());
+}
+
+@test:Config {
+ groups: ["soap11", "send_receive"]
+}
+function testSendReceiveWithUsernameTokenSecurity() returns error? {
+ Client soapClient = check new ("http://www.dneonline.com/calculator.asmx?WSDL",
+ {
+ inboundSecurity: {
+ username: "user",
+ password: "password",
+ passwordType: soap:TEXT
+ },
+ outboundSecurity: {}
+ }
+ );
+ xml body = xml `
+
+
+ 2
+ 3
+
+
+ `;
+ xml|mime:Entity[] response = check soapClient->sendReceive(body, "http://tempuri.org/Add");
+ xml expected = xml `soap:MustUnderstandSystem.Web.Services.Protocols.SoapHeaderException: SOAP header Security was not understood.
+ at System.Web.Services.Protocols.SoapHeaderHandling.SetHeaderMembers(SoapHeaderCollection headers, Object target, SoapHeaderMapping[] mappings, SoapHeaderDirection direction, Boolean client)
+ at System.Web.Services.Protocols.SoapServerProtocol.CreateServerInstance()
+ at System.Web.Services.Protocols.WebServiceHandler.Invoke()
+ at System.Web.Services.Protocols.WebServiceHandler.CoreProcessRequest()`;
+ test:assertEquals(response.toString(), expected.toString());
+}
+
+@test:Config {
+ groups: ["soap11", "send_receive"]
+}
+function testSendReceiveWithAsymmetricBindingSecurity() returns error? {
+ crypto:KeyStore serverKeyStore = {
+ path: X509_KEY_STORE_PATH,
+ password: KEY_PASSWORD
+ };
+
+ crypto:PublicKey serverPublicKey = check crypto:decodeRsaPublicKeyFromTrustStore(serverKeyStore, KEY_ALIAS);
+
+ crypto:KeyStore clientKeyStore = {
+ path: X509_KEY_STORE_PATH_2,
+ password: KEY_PASSWORD
+ };
+ crypto:PrivateKey clientPrivateKey = check crypto:decodeRsaPrivateKeyFromKeyStore(clientKeyStore, KEY_ALIAS, KEY_PASSWORD);
+
+ Client soapClient = check new ("http://www.dneonline.com/calculator.asmx?WSDL",
+ {
+ inboundSecurity: {
+ signatureAlgorithm: soap:RSA_SHA256,
+ encryptionAlgorithm: soap:RSA_ECB,
+ signatureKey: clientPrivateKey,
+ encryptionKey: serverPublicKey
+ }
+ }
+ );
+
+ xml body = xml `
+
+
+ 2
+ 3
+
+
+ `;
+ xml|mime:Entity[] response = check soapClient->sendReceive(body, "http://tempuri.org/Add");
+ xml expected = xml `soap:MustUnderstandSystem.Web.Services.Protocols.SoapHeaderException: SOAP header Security was not understood.
+ at System.Web.Services.Protocols.SoapHeaderHandling.SetHeaderMembers(SoapHeaderCollection headers, Object target, SoapHeaderMapping[] mappings, SoapHeaderDirection direction, Boolean client)
+ at System.Web.Services.Protocols.SoapServerProtocol.CreateServerInstance()
+ at System.Web.Services.Protocols.WebServiceHandler.Invoke()
+ at System.Web.Services.Protocols.WebServiceHandler.CoreProcessRequest()`;
+ test:assertEquals(response.toString(), expected.toString());
+}
+
+@test:Config {
+ groups: ["soap11", "send_receive"]
+}
+function testSendReceiveWithSymmetricBindingSecurity() returns error? {
+ crypto:KeyStore serverKeyStore = {
+ path: X509_KEY_STORE_PATH,
+ password: KEY_PASSWORD
+ };
+ crypto:PublicKey serverPublicKey = check crypto:decodeRsaPublicKeyFromTrustStore(serverKeyStore, KEY_ALIAS);
+
+ crypto:KeyStore keyStore = {
+ path: KEY_STORE_PATH,
+ password: KEY_PASSWORD
+ };
+ crypto:PrivateKey symmetricKey = check crypto:decodeRsaPrivateKeyFromKeyStore(keyStore, KEY_ALIAS, KEY_PASSWORD);
+
+ Client soapClient = check new ("http://www.dneonline.com/calculator.asmx?WSDL",
+ {
+ inboundSecurity: {
+ signatureAlgorithm: soap:RSA_SHA256,
+ encryptionAlgorithm: soap:RSA_ECB,
+ symmetricKey: symmetricKey,
+ servicePublicKey: serverPublicKey
+ }
+ }
+ );
+ xml body = xml `
+
+
+ 2
+ 3
+
+
+ `;
+ xml|mime:Entity[] response = check soapClient->sendReceive(body, "http://tempuri.org/Add");
+ xml expected = xml `soap:MustUnderstandSystem.Web.Services.Protocols.SoapHeaderException: SOAP header Security was not understood.
+ at System.Web.Services.Protocols.SoapHeaderHandling.SetHeaderMembers(SoapHeaderCollection headers, Object target, SoapHeaderMapping[] mappings, SoapHeaderDirection direction, Boolean client)
+ at System.Web.Services.Protocols.SoapServerProtocol.CreateServerInstance()
+ at System.Web.Services.Protocols.WebServiceHandler.Invoke()
+ at System.Web.Services.Protocols.WebServiceHandler.CoreProcessRequest()`;
+ test:assertEquals(response.toString(), expected.toString());
+}
+
+@test:Config {
+ groups: ["soap11", "send_receive"]
+}
+function testSoapEndpoint() returns error? {
+ string username = "user";
+ string password = "password";
+ Client soapClient = check new ("http://localhost:9090",
+ {
+ inboundSecurity: {
+ username: username,
+ password: password,
+ passwordType: wssec:TEXT
+ }
+ }
+ );
+ xml body = xml `23`;
+ xml|mime:Entity[] response = check soapClient->sendReceive(body, "http://tempuri.org/Add", path = "/getSamePayload");
+ return soap:assertUsernameToken(response.toString(), username, password, wssec:TEXT, string `23`);
+}
+
+@test:Config {
+ groups: ["soap11", "send_receive"]
+}
+function testSoapReceiveWithSymmetricBindingAndOutboundConfig() returns error? {
+ Client soapClient = check new ("http://localhost:9090",
+ {
+ inboundSecurity: {
+ signatureAlgorithm: wssec:RSA_SHA256,
+ encryptionAlgorithm: wssec:RSA_ECB,
+ symmetricKey: symmetricKey,
+ servicePublicKey: serverPublicKey
+ },
+ outboundSecurity: {
+ verificationKey: publicKey,
+ signatureAlgorithm: wssec:RSA_SHA256,
+ decryptionAlgorithm: wssec:RSA_ECB,
+ decryptionKey: publicKey
+ }
+ }
+ );
+ xml body = xml `23`;
+ xml|mime:Entity[] response = check soapClient->sendReceive(body, "http://tempuri.org/Add", path = "/getSamePayload");
+ return soap:assertSymmetricBinding(response.toString(), string `23`);
+}
+
+@test:Config {
+ groups: ["soap11", "send_receive", "j"]
+}
+function testSendReceiveWithAsymmetricBindingAndOutboundConfig() returns error? {
+ Client soapClient = check new ("http://localhost:9090",
+ {
+ inboundSecurity: {
+ signatureAlgorithm: soap:RSA_SHA256,
+ encryptionAlgorithm: soap:RSA_ECB,
+ signatureKey: clientPrivateKey,
+ encryptionKey: serverPublicKey
+ },
+ outboundSecurity: {
+ verificationKey: serverPublicKey,
+ signatureAlgorithm: soap:RSA_SHA256,
+ decryptionAlgorithm: soap:RSA_ECB,
+ decryptionKey: clientPrivateKey
+ }
+ }
+ );
+ xml body = xml `23`;
+ xml|mime:Entity[] response = check soapClient->sendReceive(body, "http://tempuri.org/Add", path = "/getSecuredPayload");
+ return soap:assertSymmetricBinding(response.toString(), string `23`);
+}
diff --git a/ballerina/modules/soap12/Module.md b/ballerina/modules/soap12/Module.md
new file mode 100644
index 0000000..97751d7
--- /dev/null
+++ b/ballerina/modules/soap12/Module.md
@@ -0,0 +1,166 @@
+## Overview
+
+This module offers a set of APIs that facilitate the transmission of XML requests to a SOAP 1.2 backend. It excels in managing security policies within SOAP requests, ensuring the transmission of secured SOAP envelopes. Moreover, it possesses the capability to efficiently extract data from security-applied SOAP responses.
+
+SOAP module abstracts out the details of the creation of a SOAP envelope, headers, and the body in a SOAP message.
+
+## Client
+
+The `Client` is used to connect to and interact with `SOAP` 1.2 endpoints.
+
+### SOAP 1.2 Client
+
+```ballerina
+import ballerina/soap:soap12;
+
+soap12:Client soapClient = check new ("http://www.dneonline.com/calculator.asmx?WSDL");
+```
+
+## APIs associated with SOAP
+
+- **Send & Receive**: Sends SOAP request and receives a response.
+- **Send Only**: Fires and forgets requests. Sends the request without the possibility of any response from the service.
+
+The SOAP 1.1 specification requires the inclusion of the `action` parameter as a mandatory component within its APIs. In contrast, SOAP 1.2 relaxes this requirement, making the action parameter optional.
+
+### Example: Send & Receive
+
+```ballerina
+import ballerina/soap:soap12;
+
+public function main() returns error? {
+ soap12:Client soapClient = check new ("http://www.dneonline.com/calculator.asmx?WSDL");
+
+ xml envelope = xml `
+
+
+ 2
+ 3
+
+
+ `;
+ xml|mime:Entity[] response = check soapClient->sendReceive(envelope, "http://tempuri.org/Add");
+}
+```
+
+### Example: Send Only
+
+```ballerina
+import ballerina/soap:soap12;
+
+public function main() returns error? {
+ soap12:Client soapClient = check new ("http://www.dneonline.com/calculator.asmx?WSDL");
+
+ xml envelope = xml `
+
+
+ 2
+ 3
+
+
+ `;
+ check soapClient->sendOnly(envelope, "http://tempuri.org/Add");
+}
+```
+## Security
+
+The SOAP client module introduces a robust framework for configuring security measures in SOAP communication. Security is a critical concern when exchanging data via web services, and this module offers comprehensive options to fortify SOAP requests and responses.
+
+There are two primary security configurations available for SOAP clients:
+
+- `inboundSecurity`: This configuration is applied to the SOAP envelope when a request is made. It includes various ws security policies such as Username Token, Timestamp Token, X509 Token, Symmetric Binding, Asymmetric Binding, and Transport Binding, either individually or in combination with each other.
+
+- `outboundSecurity`: This configuration is applied to the SOAP envelope when a response is received. Its purpose is to decrypt the data within the envelope and verify the digital signature for security validation.
+
+### Policies
+
+This library currently supports the following WS Security policies:
+
+- **Username Token**: Provides authentication through username and password credentials.
+- **Timestamp Token**: Enhances message integrity by incorporating timestamp information.
+- **X509 Token**: Allows the use of X.509 certificates for secure communication.
+- **Symmetric Binding**: Enables symmetric key-based security mechanisms.
+- **Asymmetric Binding**: Facilitates the use of asymmetric cryptography for enhanced security.
+
+These policies empower SOAP clients to enhance the security of their web service communications by selecting and implementing the appropriate security mechanisms to safeguard their SOAP envelopes.
+
+### Security Policy Configuration Types
+
+#### Inbound Security Configurations
+
+- `TimestampTokenConfig`: Represents the record for Timestamp Token policy.
+ - Fields:
+ - `int` timeToLive : The time to get expired
+
+- `UsernameTokenConfig`: Represents the record for Username Token policy.
+ - Fields:
+ - `string` username : The name of the user
+ - `string` password : The password of the user
+ - `PasswordType` passwordType : The password type of the username token
+
+- `SymmetricBindingConfig`: Represents the record for Symmetric Binding policy.
+ - Fields:
+ - `crypto:PrivateKey` symmetricKey : The key to sign and encrypt the SOAP envelope
+ - `crypto:PublicKey` servicePublicKey : The key to encrypt the symmetric key
+ - `SignatureAlgorithm` signatureAlgorithm : The algorithm to sign the SOAP envelope
+ - `EncryptionAlgorithm` encryptionAlgorithm : The algorithm to encrypt the SOAP envelope
+ - `string` x509Token : The path or token of the X509 certificate
+
+- `AsymmetricBindingConfig`: Represents the record for Username Token with Asymmetric Binding policy.
+ - Fields:
+ - `crypto:PrivateKey` signatureKey : The private key to sign the SOAP envelope
+ - `crypto:PublicKey` encryptionKey : The public key to encrypt the SOAP body
+ - `SignatureAlgorithm` signatureAlgorithm : The algorithm to sign the SOAP envelope
+ - `EncryptionAlgorithm` encryptionAlgorithm : The algorithm to encrypt the SOAP body
+ - `string` x509Token : field description
+
+#### Outbound Security Configurations
+
+- `OutboundSecurityConfig`: Represents the record for outbound security configurations to verify and decrypt SOAP envelopes.
+ - Fields:
+ - `crypto:PublicKey` verificationKey : The public key to verify the signature of the SOAP envelope
+ - `crypto:PrivateKey`|`crypto:PublicKey` decryptionKey : The private key to decrypt the SOAP envelope
+ - `SignatureAlgorithm` signatureAlgorithm : The algorithm to verify the SOAP envelope
+ - `EncryptionAlgorithm` decryptionAlgorithm : The algorithm to decrypt the SOAP body
+
+### Apply Security Policies
+
+#### SOAP 1.2 Client with Asymmetric Binding and Outbound Security Configuration
+
+```ballerina
+import ballerina/crypto;
+import ballerina/mime;
+import ballerina/soap;
+import ballerina/soap:soap12;
+
+public function main() returns error? {
+ crypto:PrivateKey clientPrivateKey = ...//
+ crypto:PublicKey clientPublicKey = ...//
+ crypto:PublicKey serverPublicKey = ...//
+
+ soap12:Client soapClient = check new ("http://www.dneonline.com/calculator.asmx?WSDL",
+ {
+ inboundSecurity: {
+ signatureAlgorithm: soap:RSA_SHA256,
+ encryptionAlgorithm: soap:RSA_ECB,
+ signatureKey: clientPrivateKey,
+ encryptionKey: serverPublicKey,
+ },
+ outboundSecurity: {
+ verificationKey: serverPublicKey,
+ signatureAlgorithm: soap:RSA_SHA256,
+ decryptionKey: clientPrivateKey,
+ decryptionAlgorithm: soap:RSA_ECB
+ }
+ });
+}
+```
+
+## Report issues
+
+To report bugs, request new features, start new discussions, view project boards, etc., go to the [Ballerina standard library parent repository](https://github.com/ballerina-platform/ballerina-standard-library).
+
+## Useful links
+
+- Chat live with us via our [Discord server](https://discord.gg/ballerinalang).
+- Post all technical questions on Stack Overflow with the [#ballerina](https://stackoverflow.com/questions/tagged/ballerina) tag.
diff --git a/ballerina/modules/soap12/error.bal b/ballerina/modules/soap12/error.bal
new file mode 100644
index 0000000..55ce7c3
--- /dev/null
+++ b/ballerina/modules/soap12/error.bal
@@ -0,0 +1,23 @@
+// Copyright (c) 2023, WSO2 LLC. (http://www.wso2.com) All Rights Reserved.
+//
+// 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.
+
+# Defines the common error type for the SOAP 1.2 module.
+public type Error distinct error;
+
+const SOAP_RESPONSE_ERROR = "Failed to create SOAP response.";
+const SOAP_CLIENT_ERROR = "Failed to initialize SOAP 1.2 client.";
+const SOAP_ERROR = "Failed to generate a response";
+const INVALID_OUTBOUND_SECURITY_ERROR = "Outbound security configurations do not match with the SOAP response.";
diff --git a/ballerina/modules/soap12/soap12.bal b/ballerina/modules/soap12/soap12.bal
new file mode 100644
index 0000000..fe8706a
--- /dev/null
+++ b/ballerina/modules/soap12/soap12.bal
@@ -0,0 +1,118 @@
+// Copyright (c) 2023, WSO2 LLC. (http://www.wso2.com) All Rights Reserved.
+//
+// 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 soap;
+import soap.wssec;
+
+import ballerina/http;
+import ballerina/mime;
+
+# Object for the basic SOAP client endpoint.
+public isolated client class Client {
+ private final http:Client soapClient;
+ private final readonly & wssec:InboundSecurityConfig|wssec:InboundSecurityConfig[] inboundSecurity;
+ private final readonly & wssec:OutboundSecurityConfig outboundSecurity;
+
+ # Gets invoked during object initialization.
+ #
+ # + url - URL endpoint
+ # + config - Configurations for SOAP client
+ # + return - `error` in case of errors or `()` otherwise
+ public isolated function init(string url, *soap:ClientConfig config) returns Error? {
+ do {
+ check soap:validateTransportBindingPolicy(config);
+ self.soapClient = check new (url, config.httpConfig);
+ readonly & soap:ClientConfig readonlyConfig = soap:getReadOnlyClientConfig(config);
+ self.inboundSecurity = readonlyConfig.inboundSecurity;
+ self.outboundSecurity = readonlyConfig.outboundSecurity;
+ } on fail var err {
+ return error Error(SOAP_CLIENT_ERROR, err);
+ }
+ }
+
+ # Sends SOAP request and expects a response.
+ # ```ballerina
+ # xml|mime:Entity[] response = check soapClient->sendReceive(body);
+ # ```
+ #
+ # + body - SOAP request body as an `XML` or `mime:Entity[]` to work with SOAP attachments
+ # + action - SOAP action as a `string`
+ # + headers - SOAP headers as a `map`
+ # + path - The resource path
+ # + return - If successful, returns the response. Else, returns an error
+ remote isolated function sendReceive(xml|mime:Entity[] body, string? action = (),
+ map headers = {}, string path = "")
+ returns xml|mime:Entity[]|Error {
+ do {
+ xml securedBody;
+ xml mimeEntity = body is xml ? body : check body[0].getXml();
+ lock {
+ securedBody = body is xml ? check soap:applySecurityPolicies(self.inboundSecurity.clone(), body.clone())
+ : check soap:applySecurityPolicies(self.inboundSecurity.clone(), mimeEntity.clone());
+ }
+ xml response;
+ if body is mime:Entity[] {
+ body[0].setXml(securedBody);
+ response = check soap:sendReceive(body, self.soapClient, action, headers, path);
+ } else {
+ response = check soap:sendReceive(securedBody, self.soapClient, action, headers, path);
+ }
+ lock {
+ wssec:OutboundSecurityConfig? outboundSecurity = self.outboundSecurity.clone();
+ do {
+ if outboundSecurity is wssec:OutboundSecurityConfig {
+ return check soap:applyOutboundConfig(outboundSecurity.clone(), response.clone());
+ }
+ } on fail var e {
+ return error Error(INVALID_OUTBOUND_SECURITY_ERROR, e.cause());
+ }
+ return response.clone();
+ }
+ } on fail var e {
+ return error Error(SOAP_ERROR, e.cause());
+ }
+ }
+
+ # Fires and forgets requests. Sends the request without the possibility of any response from the
+ # service (even an error).
+ # ```ballerina
+ # check soapClient->sendOnly(body);
+ # ```
+ #
+ # + body - SOAP request body as an `XML` or `mime:Entity[]` to work with SOAP attachments
+ # + action - SOAP action as a `string`
+ # + headers - SOAP headers as a `map`
+ # + path - The resource path
+ # + return - If successful, returns `nil`. Else, returns an error
+ remote isolated function sendOnly(xml|mime:Entity[] body, string? action = (),
+ map headers = {}, string path = "") returns Error? {
+ do {
+ xml securedBody;
+ xml mimeEntity = body is xml ? body : check body[0].getXml();
+ lock {
+ securedBody = body is xml ? check soap:applySecurityPolicies(self.inboundSecurity.clone(), body.clone())
+ : check soap:applySecurityPolicies(self.inboundSecurity.clone(), mimeEntity.clone());
+ }
+ if body is mime:Entity[] {
+ body[0].setXml(securedBody);
+ return check soap:sendOnly(body, self.soapClient, action, headers, path);
+ }
+ return check soap:sendOnly(securedBody, self.soapClient, action, headers, path);
+ } on fail var e {
+ return error Error(SOAP_ERROR, e.cause());
+ }
+ }
+}
diff --git a/ballerina/modules/soap12/tests/http_soap_service.bal b/ballerina/modules/soap12/tests/http_soap_service.bal
new file mode 100644
index 0000000..5acebab
--- /dev/null
+++ b/ballerina/modules/soap12/tests/http_soap_service.bal
@@ -0,0 +1,64 @@
+// Copyright (c) 2023, WSO2 LLC. (http://www.wso2.org) All Rights Reserved.
+//
+// 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/crypto;
+import ballerina/http;
+import ballerina/soap;
+
+const crypto:KeyStore serverKeyStore = {
+ path: X509_KEY_STORE_PATH,
+ password: KEY_PASSWORD
+};
+crypto:PrivateKey serverPrivateKey = check crypto:decodeRsaPrivateKeyFromKeyStore(serverKeyStore, KEY_ALIAS,
+ KEY_PASSWORD);
+crypto:PublicKey serverPublicKey = check crypto:decodeRsaPublicKeyFromTrustStore(serverKeyStore, KEY_ALIAS);
+
+service / on new http:Listener(9090) {
+
+ resource function post getPayload(http:Request request) returns http:Response|error {
+ http:Response response = new;
+ response.setPayload(check (check request.getBodyParts())[0].getXml());
+ return response;
+ }
+
+ resource function post getSamePayload(http:Request request) returns http:Response|error {
+ xml payload = check request.getXmlPayload();
+ http:Response response = new;
+ response.setPayload(payload);
+ return response;
+ }
+
+ resource function post getSecuredPayload(http:Request request) returns http:Response|error {
+ xml payload = check request.getXmlPayload();
+ xml applyOutboundConfig = check soap:applyOutboundConfig(
+ {
+ verificationKey: clientPublicKey,
+ signatureAlgorithm: soap:RSA_SHA256,
+ decryptionAlgorithm: soap:RSA_ECB,
+ decryptionKey: serverPrivateKey
+ }, payload);
+ xml securedEnv = check soap:applySecurityPolicies(
+ {
+ signatureAlgorithm: soap:RSA_SHA256,
+ encryptionAlgorithm: soap:RSA_ECB,
+ signatureKey: serverPrivateKey,
+ encryptionKey: clientPublicKey
+ }, applyOutboundConfig);
+ http:Response response = new;
+ response.setPayload(securedEnv);
+ return response;
+ }
+}
diff --git a/ballerina/modules/soap12/tests/soap12_client_test.bal b/ballerina/modules/soap12/tests/soap12_client_test.bal
new file mode 100644
index 0000000..500e006
--- /dev/null
+++ b/ballerina/modules/soap12/tests/soap12_client_test.bal
@@ -0,0 +1,444 @@
+// Copyright (c) 2023, WSO2 LLC. (http://www.wso2.com) All Rights Reserved.
+//
+// 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 soap;
+import soap.wssec;
+
+import ballerina/crypto;
+import ballerina/mime;
+import ballerina/test;
+
+const KEY_ALIAS = "wss40";
+const KEY_PASSWORD = "security";
+const KEY_STORE_PATH = "modules/wssec/tests/resources/wss40.p12";
+const X509_KEY_STORE_PATH = "modules/wssec/tests/resources/x509_certificate.p12";
+const X509_KEY_STORE_PATH_2 = "modules/wssec/tests/resources/x509_certificate_2.p12";
+const wssec:TransportBindingConfig TRANSPORT_BINDING = "TransportBinding";
+const wssec:NoPolicy NO_POLICY = "NoPolicy";
+
+const crypto:KeyStore clientKeyStore = {
+ path: X509_KEY_STORE_PATH_2,
+ password: KEY_PASSWORD
+};
+crypto:PrivateKey clientPrivateKey = check crypto:decodeRsaPrivateKeyFromKeyStore(clientKeyStore, KEY_ALIAS,
+ KEY_PASSWORD);
+crypto:PublicKey clientPublicKey = check crypto:decodeRsaPublicKeyFromTrustStore(clientKeyStore, KEY_ALIAS);
+
+crypto:KeyStore keyStore = {
+ path: KEY_STORE_PATH,
+ password: KEY_PASSWORD
+};
+crypto:PrivateKey symmetricKey = check crypto:decodeRsaPrivateKeyFromKeyStore(keyStore, KEY_ALIAS, KEY_PASSWORD);
+crypto:PublicKey publicKey = check crypto:decodeRsaPublicKeyFromTrustStore(keyStore, KEY_ALIAS);
+
+@test:Config {
+ groups: ["soap12", "send_only"]
+}
+function testSendOnly12() returns error? {
+ xml body = xml `
+
+
+ 2
+ 3
+
+
+ `;
+
+ Client soapClient = check new ("http://www.dneonline.com/calculator.asmx?WSDL");
+
+ check soapClient->sendOnly(body, "http://tempuri.org/Add");
+}
+
+@test:Config {
+ groups: ["soap12", "send_receive"]
+}
+function testSendReceive12() returns error? {
+ Client soapClient = check new ("http://www.dneonline.com/calculator.asmx?WSDL");
+ xml body = xml `
+
+
+ 2
+ 3
+
+
+ `;
+ xml|mime:Entity[] response = check soapClient->sendReceive(body, "http://tempuri.org/Add");
+
+ xml expected = xml `5`;
+ test:assertEquals(response, expected);
+}
+
+@test:Config {
+ groups: ["soap12", "send_receive"]
+}
+function testSendReceive12WithHeaders() returns error? {
+ xml body = xml `
+
+
+ 2
+ 3
+
+
+ `;
+
+ Client soapClient = check new ("http://www.dneonline.com/calculator.asmx?WSDL");
+
+ xml|mime:Entity[] response = check soapClient->sendReceive(body, "http://tempuri.org/Add",
+ {foo: ["bar1", "bar2"]});
+
+ xml expected = xml `5`;
+ test:assertEquals(response, expected);
+}
+
+@test:Config {
+ groups: ["soap12", "send_receive"]
+}
+function testSendReceive12WithoutSoapAction() returns error? {
+ xml body = xml `
+
+
+ 2
+ 3
+
+
+ `;
+
+ Client soapClient = check new ("http://www.dneonline.com/calculator.asmx?WSDL");
+
+ xml|mime:Entity[] response = check soapClient->sendReceive(body);
+
+ xml expected = xml `5`;
+ test:assertEquals(response, expected);
+}
+
+@test:Config {
+ groups: ["soap12", "send_only"]
+}
+function testSendOnly12WithoutSoapAction() returns error? {
+ xml body = xml `
+
+
+ 2
+ 3
+
+
+ `;
+
+ Client soapClient = check new ("http://www.dneonline.com/calculator.asmx?WSDL");
+
+ check soapClient->sendOnly(body);
+}
+
+@test:Config {
+ groups: ["soap12", "send_receive"]
+}
+function testSendReceive12IncludingHeadersWithoutSoapAction() returns error? {
+ xml body = xml `
+
+
+ 2
+ 3
+
+
+ `;
+
+ Client soapClient = check new ("http://www.dneonline.com/calculator.asmx?WSDL");
+
+ xml|mime:Entity[] response = check soapClient->sendReceive(body, (), {foo: ["bar1", "bar2"]});
+ xml expected = xml `5`;
+ test:assertEquals(response, expected);
+}
+
+@test:Config {
+ groups: ["soap12"]
+}
+function testTransportBindingError() returns error? {
+ Client|Error soapClient = new ("http://www.dneonline.com/calculator.asmx?WSDL", inboundSecurity = TRANSPORT_BINDING);
+ test:assertTrue(soapClient is Error);
+ test:assertEquals((soapClient).message(), SOAP_CLIENT_ERROR);
+}
+
+@test:Config {
+ groups: ["soap12"]
+}
+function testTransportBindingError2() returns error? {
+ Client|Error soapClient = new ("http://www.dneonline.com/calculator.asmx?WSDL",
+ inboundSecurity = [
+ TRANSPORT_BINDING
+ ]
+ );
+ test:assertTrue(soapClient is Error);
+ test:assertEquals((soapClient).message(), SOAP_CLIENT_ERROR);
+}
+
+@test:Config {
+ groups: ["soap12", "send_receive"]
+}
+function testSendReceiveError() returns error? {
+ Client soapClient = check new ("http://www.dneonline.com/invalidcalculator.asmx?WSDL");
+ xml body = xml `
+
+
+ 2
+ 3
+
+
+ `;
+ xml|mime:Entity[]|Error response = soapClient->sendReceive(body, "http://tempuri.org/Add");
+ test:assertTrue(response is Error);
+ test:assertEquals((response).message(), SOAP_ERROR);
+}
+
+@test:Config {
+ groups: ["soap12", "send_receive"]
+}
+function testSendReceiveWithTimestampTokenSecurity() returns error? {
+ Client soapClient = check new ("http://www.dneonline.com/calculator.asmx?WSDL",
+ {
+ inboundSecurity: [
+ {
+ timeToLive: 600
+ }
+ ]
+ }
+ );
+ xml body = xml `
+
+
+ 2
+ 3
+
+
+ `;
+ xml|mime:Entity[] response = check soapClient->sendReceive(body, "http://tempuri.org/Add");
+ xml expected = xml `soap:MustUnderstandSystem.Web.Services.Protocols.SoapHeaderException: SOAP header Security was not understood.
+ at System.Web.Services.Protocols.SoapHeaderHandling.SetHeaderMembers(SoapHeaderCollection headers, Object target, SoapHeaderMapping[] mappings, SoapHeaderDirection direction, Boolean client)
+ at System.Web.Services.Protocols.SoapServerProtocol.CreateServerInstance()
+ at System.Web.Services.Protocols.WebServiceHandler.Invoke()
+ at System.Web.Services.Protocols.WebServiceHandler.CoreProcessRequest()`;
+
+ test:assertEquals(response.toString(), expected.toString());
+}
+
+@test:Config {
+ groups: ["soap12", "send_receive"]
+}
+function testSendReceiveWithUsernameTokenSecurity() returns error? {
+ Client soapClient = check new ("http://www.dneonline.com/calculator.asmx?WSDL",
+ {
+ inboundSecurity: {
+ username: "user",
+ password: "password",
+ passwordType: soap:TEXT
+ },
+ outboundSecurity: {}
+ }
+ );
+ xml body = xml `
+
+
+ 2
+ 3
+
+
+ `;
+ xml|mime:Entity[] response = check soapClient->sendReceive(body, "http://tempuri.org/Add");
+ xml expected = xml `soap:MustUnderstandSystem.Web.Services.Protocols.SoapHeaderException: SOAP header Security was not understood.
+ at System.Web.Services.Protocols.SoapHeaderHandling.SetHeaderMembers(SoapHeaderCollection headers, Object target, SoapHeaderMapping[] mappings, SoapHeaderDirection direction, Boolean client)
+ at System.Web.Services.Protocols.SoapServerProtocol.CreateServerInstance()
+ at System.Web.Services.Protocols.WebServiceHandler.Invoke()
+ at System.Web.Services.Protocols.WebServiceHandler.CoreProcessRequest()`;
+
+ test:assertEquals(response.toString(), expected.toString());
+}
+
+@test:Config {
+ groups: ["soap12", "send_receive"]
+}
+function testSendReceiveWithAsymmetricBindingSecurity() returns error? {
+ crypto:KeyStore serverKeyStore = {
+ path: X509_KEY_STORE_PATH,
+ password: KEY_PASSWORD
+ };
+
+ crypto:PublicKey serverPublicKey = check crypto:decodeRsaPublicKeyFromTrustStore(serverKeyStore, KEY_ALIAS);
+
+ crypto:KeyStore clientKeyStore = {
+ path: X509_KEY_STORE_PATH_2,
+ password: KEY_PASSWORD
+ };
+ crypto:PrivateKey clientPrivateKey = check crypto:decodeRsaPrivateKeyFromKeyStore(clientKeyStore, KEY_ALIAS, KEY_PASSWORD);
+
+ Client soapClient = check new ("http://www.dneonline.com/calculator.asmx?WSDL",
+ {
+ inboundSecurity: {
+ signatureAlgorithm: soap:RSA_SHA256,
+ encryptionAlgorithm: soap:RSA_ECB,
+ signatureKey: clientPrivateKey,
+ encryptionKey: serverPublicKey
+ }
+ }
+ );
+ xml body = xml `
+
+
+ 2
+ 3
+
+
+ `;
+ xml|mime:Entity[] response = check soapClient->sendReceive(body, "http://tempuri.org/Add");
+ xml expected = xml `soap:MustUnderstandSystem.Web.Services.Protocols.SoapHeaderException: SOAP header Security was not understood.
+ at System.Web.Services.Protocols.SoapHeaderHandling.SetHeaderMembers(SoapHeaderCollection headers, Object target, SoapHeaderMapping[] mappings, SoapHeaderDirection direction, Boolean client)
+ at System.Web.Services.Protocols.SoapServerProtocol.CreateServerInstance()
+ at System.Web.Services.Protocols.WebServiceHandler.Invoke()
+ at System.Web.Services.Protocols.WebServiceHandler.CoreProcessRequest()`;
+
+ test:assertEquals(response.toString(), expected.toString());
+}
+
+@test:Config {
+ groups: ["soap12", "send_receive"]
+}
+function testSendReceiveWithSymmetricBindingSecurity() returns error? {
+ crypto:KeyStore serverKeyStore = {
+ path: X509_KEY_STORE_PATH,
+ password: KEY_PASSWORD
+ };
+ crypto:PublicKey serverPublicKey = check crypto:decodeRsaPublicKeyFromTrustStore(serverKeyStore, KEY_ALIAS);
+
+ crypto:KeyStore keyStore = {
+ path: KEY_STORE_PATH,
+ password: KEY_PASSWORD
+ };
+ crypto:PrivateKey symmetricKey = check crypto:decodeRsaPrivateKeyFromKeyStore(keyStore, KEY_ALIAS, KEY_PASSWORD);
+
+ Client soapClient = check new ("http://www.dneonline.com/calculator.asmx?WSDL",
+ {
+ inboundSecurity: {
+ signatureAlgorithm: soap:RSA_SHA256,
+ encryptionAlgorithm: soap:RSA_ECB,
+ symmetricKey: symmetricKey,
+ servicePublicKey: serverPublicKey
+ }
+ }
+ );
+ xml body = xml `
+
+
+ 2
+ 3
+
+
+ `;
+ xml|mime:Entity[] response = check soapClient->sendReceive(body, "http://tempuri.org/Add");
+ xml expected = xml `soap:MustUnderstandSystem.Web.Services.Protocols.SoapHeaderException: SOAP header Security was not understood.
+ at System.Web.Services.Protocols.SoapHeaderHandling.SetHeaderMembers(SoapHeaderCollection headers, Object target, SoapHeaderMapping[] mappings, SoapHeaderDirection direction, Boolean client)
+ at System.Web.Services.Protocols.SoapServerProtocol.CreateServerInstance()
+ at System.Web.Services.Protocols.WebServiceHandler.Invoke()
+ at System.Web.Services.Protocols.WebServiceHandler.CoreProcessRequest()`;
+ test:assertEquals(response.toString(), expected.toString());
+}
+
+function testSoapEndpoint() returns error? {
+ string username = "user";
+ string password = "password";
+ Client soapClient = check new ("http://localhost:9090",
+ {
+ inboundSecurity: {
+ username: username,
+ password: password,
+ passwordType: wssec:TEXT
+ }
+ }
+ );
+ xml body = xml `23`;
+ xml|mime:Entity[] response = check soapClient->sendReceive(body, "http://tempuri.org/Add", path = "/getSamePayload");
+ return soap:assertUsernameToken(response.toString(), username, password, wssec:TEXT, string `23`);
+}
+
+@test:Config {
+ groups: ["soap11", "send_receive"]
+}
+function testSoapReceiveWithSymmetricBindingAndOutboundConfig() returns error? {
+ Client soapClient = check new ("http://localhost:9090",
+ {
+ inboundSecurity: {
+ signatureAlgorithm: wssec:RSA_SHA256,
+ encryptionAlgorithm: wssec:RSA_ECB,
+ symmetricKey: symmetricKey,
+ servicePublicKey: serverPublicKey
+ },
+ outboundSecurity: {
+ verificationKey: publicKey,
+ signatureAlgorithm: wssec:RSA_SHA256,
+ decryptionAlgorithm: wssec:RSA_ECB,
+ decryptionKey: publicKey
+ }
+ }
+ );
+ xml body = xml `23`;
+ xml|mime:Entity[] response = check soapClient->sendReceive(body, "http://tempuri.org/Add", path = "/getSamePayload");
+ return soap:assertSymmetricBinding(response.toString(), string `23`);
+}
+
+@test:Config {
+ groups: ["soap11", "send_receive", "j"]
+}
+function testSendReceiveWithAsymmetricBindingAndOutboundConfig() returns error? {
+ Client soapClient = check new ("http://localhost:9090",
+ {
+ inboundSecurity: {
+ signatureAlgorithm: soap:RSA_SHA256,
+ encryptionAlgorithm: soap:RSA_ECB,
+ signatureKey: clientPrivateKey,
+ encryptionKey: serverPublicKey
+ },
+ outboundSecurity: {
+ verificationKey: serverPublicKey,
+ signatureAlgorithm: soap:RSA_SHA256,
+ decryptionAlgorithm: soap:RSA_ECB,
+ decryptionKey: clientPrivateKey
+ }
+ }
+ );
+
+ xml body = xml `23`;
+ xml|mime:Entity[] response = check soapClient->sendReceive(body, "http://tempuri.org/Add", path = "/getSecuredPayload");
+ return soap:assertSymmetricBinding(response.toString(), string `23`);
+}
diff --git a/ballerina/modules/wssec/document.bal b/ballerina/modules/wssec/document.bal
new file mode 100644
index 0000000..d70dd38
--- /dev/null
+++ b/ballerina/modules/wssec/document.bal
@@ -0,0 +1,39 @@
+// Copyright (c) 2023, WSO2 LLC. (http://www.wso2.com) All Rights Reserved.
+//
+// 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;
+
+isolated class Document {
+
+ public isolated function init(xml xmlPayload) returns Error? {
+ handle|error documentBuilder = newDocument(self, xmlPayload);
+ if documentBuilder is error {
+ return error Error(documentBuilder.message());
+ }
+ }
+
+ public isolated function getEncryptedData() returns byte[] = @java:Method {
+ 'class: "org.wssec.DocumentBuilder"
+ } external;
+
+ public isolated function getSignatureData() returns byte[] = @java:Method {
+ 'class: "org.wssec.DocumentBuilder"
+ } external;
+}
+
+isolated function newDocument(Document doc, xml xmlPayload) returns handle|error = @java:Constructor {
+ 'class: "org.wssec.DocumentBuilder"
+} external;
diff --git a/ballerina/modules/wssec/encryption.bal b/ballerina/modules/wssec/encryption.bal
new file mode 100644
index 0000000..7246866
--- /dev/null
+++ b/ballerina/modules/wssec/encryption.bal
@@ -0,0 +1,42 @@
+// Copyright (c) 2023, WSO2 LLC. (http://www.wso2.com) All Rights Reserved.
+//
+// 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;
+
+isolated class Encryption {
+
+ private handle nativeEncryption;
+
+ isolated function init() returns Error? {
+ self.nativeEncryption = newEncryption();
+ }
+
+ public isolated function setEncryptionAlgorithm(string encryptionAlgorithm) = @java:Method {
+ 'class: "org.wssec.Encryption"
+ } external;
+
+ public isolated function setEncryptedData(byte[] encryptedData) = @java:Method {
+ 'class: "org.wssec.Encryption"
+ } external;
+
+ public isolated function getEncryptedKeyElements(byte[] encryptedKey) returns string|Error = @java:Method {
+ 'class: "org.wssec.Encryption"
+ } external;
+}
+
+isolated function newEncryption() returns handle = @java:Constructor {
+ 'class: "org.wssec.Encryption"
+} external;
diff --git a/ballerina/modules/wssec/error.bal b/ballerina/modules/wssec/error.bal
new file mode 100644
index 0000000..65baeb3
--- /dev/null
+++ b/ballerina/modules/wssec/error.bal
@@ -0,0 +1,18 @@
+// Copyright (c) 2023, WSO2 LLC. (http://www.wso2.com) All Rights Reserved.
+//
+// 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.
+
+# Represents any error related to the wssec module.
+public type Error distinct error;
diff --git a/ballerina/modules/wssec/init.bal b/ballerina/modules/wssec/init.bal
new file mode 100644
index 0000000..9780582
--- /dev/null
+++ b/ballerina/modules/wssec/init.bal
@@ -0,0 +1,25 @@
+// Copyright (c) 2023, WSO2 LLC. (http://www.wso2.com) All Rights Reserved.
+//
+// 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;
+
+isolated function init() {
+ setModule();
+}
+
+isolated function setModule() = @java:Method {
+ 'class: "org.wssec.ModuleUtils"
+} external;
diff --git a/ballerina/modules/wssec/records.bal b/ballerina/modules/wssec/records.bal
new file mode 100644
index 0000000..8d9a3be
--- /dev/null
+++ b/ballerina/modules/wssec/records.bal
@@ -0,0 +1,89 @@
+// Copyright (c) 2023, WSO2 LLC. (http://www.wso2.com) All Rights Reserved.
+//
+// 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/crypto;
+
+# Union type of all the inbound web service security configurations.
+public type InboundSecurityConfig NoPolicy|UsernameTokenConfig|TimestampTokenConfig|SymmetricBindingConfig
+ |AsymmetricBindingConfig|TransportBindingConfig;
+
+# Represents the record for outbound security configurations to verify and decrypt SOAP envelopes.
+#
+# + verificationKey - The public key to verify the signature of the SOAP envelope
+# + decryptionKey - The private key to decrypt the SOAP envelope
+# + signatureAlgorithm - The algorithm to verify the SOAP envelope
+# + decryptionAlgorithm - The algorithm to decrypt the SOAP body
+public type OutboundSecurityConfig record {|
+ crypto:PublicKey verificationKey?;
+ crypto:PrivateKey|crypto:PublicKey decryptionKey?;
+ SignatureAlgorithm signatureAlgorithm?;
+ EncryptionAlgorithm decryptionAlgorithm?;
+|};
+
+# Represents the record for Username Token policy.
+#
+# + username - The name of the user
+# + password - The password of the user
+# + passwordType - The password type of the username token
+public type UsernameTokenConfig record {|
+ string username;
+ string password;
+ PasswordType passwordType;
+|};
+
+# Represents the record for Timestamp Token policy.
+#
+# + timeToLive - The time to get expired
+public type TimestampTokenConfig record {|
+ int timeToLive = 300;
+|};
+
+# Represents the record for Symmetric Binding policy.
+#
+# + symmetricKey - The key to sign and encrypt the SOAP envelope
+# + servicePublicKey - The key to encrypt the symmetric key
+# + signatureAlgorithm - The algorithm to sign the SOAP envelope
+# + encryptionAlgorithm - The algorithm to encrypt the SOAP envelope
+# + x509Token - The path or token of the X509 certificate
+public type SymmetricBindingConfig record {|
+ crypto:PrivateKey symmetricKey;
+ crypto:PublicKey servicePublicKey;
+ SignatureAlgorithm signatureAlgorithm?;
+ EncryptionAlgorithm encryptionAlgorithm?;
+ string x509Token?;
+|};
+
+# Represents the record for Username Token with Asymmetric Binding policy.
+#
+# + signatureKey - The private key to sign the SOAP envelope
+# + encryptionKey - The public key to encrypt the SOAP body
+# + signatureAlgorithm - The algorithm to sign the SOAP envelope
+# + encryptionAlgorithm - The algorithm to encrypt the SOAP body
+# + x509Token - field description
+public type AsymmetricBindingConfig record {|
+ crypto:PrivateKey signatureKey?;
+ crypto:PublicKey encryptionKey?;
+ SignatureAlgorithm signatureAlgorithm?;
+ EncryptionAlgorithm encryptionAlgorithm?;
+ string x509Token?;
+|};
+
+# Represents the record for Transport Binding policy.
+# + protocol - Protocol of the endpoint
+public type TransportBindingConfig "TransportBinding";
+
+# Represents the record to send SOAP envelopes with no security policy.
+public type NoPolicy "NoPolicy";
diff --git a/ballerina/modules/wssec/sec_header.bal b/ballerina/modules/wssec/sec_header.bal
new file mode 100644
index 0000000..77137ab
--- /dev/null
+++ b/ballerina/modules/wssec/sec_header.bal
@@ -0,0 +1,34 @@
+// Copyright (c) 2023, WSO2 LLC. (http://www.wso2.com) All Rights Reserved.
+//
+// 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;
+
+isolated class WSSecurityHeader {
+
+ private handle nativeSecHeader;
+
+ isolated function init(Document document) returns Error? {
+ self.nativeSecHeader = newSecHeader(document);
+ }
+
+ isolated function insertSecHeader() returns Error? = @java:Method {
+ 'class: "org.wssec.WsSecurityHeader"
+ } external;
+}
+
+isolated function newSecHeader(Document document) returns handle = @java:Constructor {
+ 'class: "org.wssec.WsSecurityHeader"
+} external;
diff --git a/ballerina/modules/wssec/signature.bal b/ballerina/modules/wssec/signature.bal
new file mode 100644
index 0000000..99d52be
--- /dev/null
+++ b/ballerina/modules/wssec/signature.bal
@@ -0,0 +1,84 @@
+// Copyright (c) 2023, WSO2 LLC. (http://www.wso2.com) All Rights Reserved.
+//
+// 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/crypto;
+import ballerina/jballerina.java;
+
+isolated class Signature {
+
+ private handle nativeSignature;
+
+ isolated function init() returns Error? {
+ self.nativeSignature = newSignature();
+ }
+
+ public isolated function signData(string dataString, SignatureAlgorithm signatureAlgorithm,
+ crypto:PrivateKey privateKey) returns byte[]|Error {
+ byte[] data = dataString.toBytes();
+ do {
+ match signatureAlgorithm {
+ RSA_SHA1 => {
+ return check crypto:signRsaSha1(data, privateKey);
+ }
+ RSA_SHA256 => {
+ return check crypto:signRsaSha256(data, privateKey);
+ }
+ RSA_SHA384 => {
+ return check crypto:signRsaSha384(data, privateKey);
+ }
+ _ => {
+ return check crypto:signRsaSha512(data, privateKey);
+ }
+ }
+ } on fail var e {
+ return error Error("Error occurred while signing the data", e.cause());
+ }
+ }
+
+ public isolated function verifySignature(byte[] data, byte[] signature, crypto:PublicKey publicKey,
+ SignatureAlgorithm signatureAlgorithm) returns boolean|Error {
+ do {
+ match signatureAlgorithm {
+ RSA_SHA1 => {
+ return check crypto:verifyRsaSha1Signature(data, signature, publicKey);
+ }
+ RSA_SHA256 => {
+ return check crypto:verifyRsaSha256Signature(data, signature, publicKey);
+ }
+ RSA_SHA384 => {
+ return check crypto:verifyRsaSha384Signature(data, signature, publicKey);
+ }
+ _ => {
+ return check crypto:verifyRsaSha512Signature(data, signature, publicKey);
+ }
+ }
+ } on fail var e {
+ return error Error("Error occurred while verifying the signature", e.cause());
+ }
+ }
+
+ public isolated function setSignatureAlgorithm(string signatureAlgorithm) = @java:Method {
+ 'class: "org.wssec.Signature"
+ } external;
+
+ public isolated function setSignatureValue(byte[] signatureValue) = @java:Method {
+ 'class: "org.wssec.Signature"
+ } external;
+}
+
+isolated function newSignature() returns handle = @java:Constructor {
+ 'class: "org.wssec.Signature"
+} external;
diff --git a/ballerina/modules/wssec/tests/resources/private_key.pem b/ballerina/modules/wssec/tests/resources/private_key.pem
new file mode 100644
index 0000000..3ce4123
Binary files /dev/null and b/ballerina/modules/wssec/tests/resources/private_key.pem differ
diff --git a/ballerina/modules/wssec/tests/resources/wss40.p12 b/ballerina/modules/wssec/tests/resources/wss40.p12
new file mode 100644
index 0000000..40b3924
Binary files /dev/null and b/ballerina/modules/wssec/tests/resources/wss40.p12 differ
diff --git a/ballerina/modules/wssec/tests/resources/x509_certificate.crt b/ballerina/modules/wssec/tests/resources/x509_certificate.crt
new file mode 100644
index 0000000..251c423
--- /dev/null
+++ b/ballerina/modules/wssec/tests/resources/x509_certificate.crt
@@ -0,0 +1,21 @@
+-----BEGIN CERTIFICATE-----
+MIIDazCCAlOgAwIBAgIUHYnC3ALHIdLNO+JlFu/8usNrq2owDQYJKoZIhvcNAQEL
+BQAwRTELMAkGA1UEBhMCQVUxEzARBgNVBAgMClNvbWUtU3RhdGUxITAfBgNVBAoM
+GEludGVybmV0IFdpZGdpdHMgUHR5IEx0ZDAeFw0yMzA4MjUwNzE5NDVaFw0yNDA4
+MjQwNzE5NDVaMEUxCzAJBgNVBAYTAkFVMRMwEQYDVQQIDApTb21lLVN0YXRlMSEw
+HwYDVQQKDBhJbnRlcm5ldCBXaWRnaXRzIFB0eSBMdGQwggEiMA0GCSqGSIb3DQEB
+AQUAA4IBDwAwggEKAoIBAQDdfueteXm2iSP+Pjr0+tK4G5pCxshqq0Z/ldDRpMzx
+vCAOFWIDS/ewcigUx5gMM7KHZfSzVXjlxahF5gqDbriunTSymFPfAQHimM13q1XZ
+uBCdHh1ayKmXuP4H/T1zp35rfElyGlfocWNx/Lef4y5ZFJN0BMScVdtue4+vCtst
+5NLOBaFBoZU3DGk7muu6+7+ml8P4WqSW7CR0usTvrmPssDHUqXCA7vARlGoGgTHy
+trp7e7epNf5AZD3QjSWpLEIb6DuY9LyQYeC2Yry6GGN129lNJjh4HsXG6ldu0QQU
+rCkma6oP9JSCHI0Dm0i0lDVTFYLjJ60tap7r5dXEKBXRAgMBAAGjUzBRMB0GA1Ud
+DgQWBBTEWR0+4AmZuo10dDXp0D9bVeDGlDAfBgNVHSMEGDAWgBTEWR0+4AmZuo10
+dDXp0D9bVeDGlDAPBgNVHRMBAf8EBTADAQH/MA0GCSqGSIb3DQEBCwUAA4IBAQAi
+NlCjK/OQaKPanfyacR+v8k3RU4qs7CJJ78el8iN7ZWAQn9Z3o5VIwnHWCNYIBZr9
+CWSzV72EO7j9qXSLkbQL594a6+lDSxF7vwsEx8N1ktxz4Dkg0GPyrViBDTlcloOG
+g5/ZNKsP++QczspdnJuyOTCL3DLd/P1d3WzfJF+NefvtDhmcpZxZL1YPKpeAobqf
+nRLQVeWhZbNn/XbiWnrwx4LfN+r8aQh5kiO4xl31lPzg9cflPGhTHqG3Undeis9k
+upnsjgsfBNas54lgYpOadP0f8Ooc0nJvT7ry0LbUgbkk7dGkWm9Qh2eu2e5v7Fw8
+0qWPT/H83ANtk/4Y5XCA
+-----END CERTIFICATE-----
diff --git a/ballerina/modules/wssec/tests/resources/x509_certificate.p12 b/ballerina/modules/wssec/tests/resources/x509_certificate.p12
new file mode 100644
index 0000000..2b3eddb
Binary files /dev/null and b/ballerina/modules/wssec/tests/resources/x509_certificate.p12 differ
diff --git a/ballerina/modules/wssec/tests/resources/x509_certificate_2.crt b/ballerina/modules/wssec/tests/resources/x509_certificate_2.crt
new file mode 100644
index 0000000..7913909
--- /dev/null
+++ b/ballerina/modules/wssec/tests/resources/x509_certificate_2.crt
@@ -0,0 +1,19 @@
+-----BEGIN CERTIFICATE-----
+MIIDETCCAfkCFDKN6J0IJE2sBxy0yoWYwwpBdhxBMA0GCSqGSIb3DQEBCwUAMEUx
+CzAJBgNVBAYTAkFVMRMwEQYDVQQIDApTb21lLVN0YXRlMSEwHwYDVQQKDBhJbnRl
+cm5ldCBXaWRnaXRzIFB0eSBMdGQwHhcNMjMwOTAzMjEyNjI1WhcNMjMxMDAzMjEy
+NjI1WjBFMQswCQYDVQQGEwJBVTETMBEGA1UECAwKU29tZS1TdGF0ZTEhMB8GA1UE
+CgwYSW50ZXJuZXQgV2lkZ2l0cyBQdHkgTHRkMIIBIjANBgkqhkiG9w0BAQEFAAOC
+AQ8AMIIBCgKCAQEAxG2CEtglz5hRwN3Yybb1gnsd5eJNBAm2nlzPFL1tazM6eAg1
+k5QggDKh5VV9XIAejEv3xVnSMACWgKEJvwDJjNivta7qTBOqPi/EM3oj0CR3IK4/
+U51p2z3JPaVSRapmkXpJX1k5Bf6n2n7laztOJxj2wwt5LkgJAmjDXLuiVScnPyzo
+Nsg08R8BBFqmFWI+ulyaR0Kza+m70e2K9Cj6s2C3thPyEQ4252qZY0JdgftGQmL2
+QGsMYEDmTNT0caZHbosdw1AlQ1l2JXcAfi2T07iXJkojSWVzf1SkVJ6A4j1+J3ON
+pT9zutZzjpkGRJkGxs1A4jZKWoSxXSv5J7KObwIDAQABMA0GCSqGSIb3DQEBCwUA
+A4IBAQAIKHVsFujOyXhn8nWenrg8xgJHJ1t76i1h91S8dtJz12u3OjvhHKt78mZq
+DOtOWKevY/h/StGu5Qmm/hDfTRKA4g+fIDYIf/WGAs6PO3Kac1GfWaTAwtDBfeP2
+Sl4Jy47w5oAlUPu0LKszrpN/tPVfteSvhuo/ObTnj6lW4z/nArzPrrTDirzrpxVW
+CIJrTpKr2N8EoBufE2XVA54w249nVjd1pPgPJ8bpBVD2UgNZ95nZDYCCTKb4CDdc
+JZ69CxnWq+SwRk4Gy0A3YOIjzocTzEDaGlBYaSd33zRDbOuEb8S85Tg3dzATPPtm
+7yQGi2sB6Lrs3qL9lc2fE/t7Sjgj
+-----END CERTIFICATE-----
diff --git a/ballerina/modules/wssec/tests/resources/x509_certificate_2.p12 b/ballerina/modules/wssec/tests/resources/x509_certificate_2.p12
new file mode 100644
index 0000000..ce12b92
Binary files /dev/null and b/ballerina/modules/wssec/tests/resources/x509_certificate_2.p12 differ
diff --git a/ballerina/modules/wssec/tests/test_utils.bal b/ballerina/modules/wssec/tests/test_utils.bal
new file mode 100644
index 0000000..93eef26
--- /dev/null
+++ b/ballerina/modules/wssec/tests/test_utils.bal
@@ -0,0 +1,141 @@
+// Copyright (c) 2023, WSO2 LLC. (http://www.wso2.com) All Rights Reserved.
+//
+// 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/test;
+import ballerina/crypto;
+
+const USERNAME = "username";
+const PASSWORD = "password";
+
+const KEY_ALIAS = "wss40";
+const KEY_PASSWORD = "security";
+
+const PUBLIC_KEY_PATH = "modules/wssec/tests/resources/public_key.cer";
+const PRIVATE_KEY_PATH = "modules/wssec/tests/resources/private_key.pem";
+const KEY_STORE_PATH = "modules/wssec/tests/resources/wss40.p12";
+const X509_PUBLIC_CERT_PATH = "modules/wssec/tests/resources/x509_certificate.crt";
+const X509_PUBLIC_CERT_PATH_2 = "modules/wssec/tests/resources/x509_certificate_2.crt";
+const X509_KEY_STORE_PATH = "modules/wssec/tests/resources/x509_certificate.p12";
+const X509_KEY_STORE_PATH_2 = "modules/wssec/tests/resources/x509_certificate_2.p12";
+
+const crypto:KeyStore clientKeyStore = {
+ path: X509_KEY_STORE_PATH_2,
+ password: KEY_PASSWORD
+};
+crypto:PrivateKey clientPrivateKey = check crypto:decodeRsaPrivateKeyFromKeyStore(clientKeyStore, KEY_ALIAS,
+ KEY_PASSWORD);
+crypto:PublicKey clientPublicKey = check crypto:decodeRsaPublicKeyFromTrustStore(clientKeyStore, KEY_ALIAS);
+
+const crypto:KeyStore serverKeyStore = {
+ path: X509_KEY_STORE_PATH,
+ password: KEY_PASSWORD
+};
+crypto:PrivateKey serverPrivateKey = check crypto:decodeRsaPrivateKeyFromKeyStore(serverKeyStore, KEY_ALIAS,
+ KEY_PASSWORD);
+crypto:PublicKey serverPublicKey = check crypto:decodeRsaPublicKeyFromTrustStore(serverKeyStore, KEY_ALIAS);
+
+crypto:KeyStore keyStore = {
+ path: KEY_STORE_PATH,
+ password: KEY_PASSWORD
+};
+crypto:PrivateKey symmetricKey = check crypto:decodeRsaPrivateKeyFromKeyStore(keyStore, KEY_ALIAS, KEY_PASSWORD);
+crypto:PublicKey publicKey = check crypto:decodeRsaPublicKeyFromTrustStore(keyStore, KEY_ALIAS);
+
+function assertTimestampToken(string envelopeString) {
+ string:RegExp ts_token = re ``;
+ string:RegExp created = re `.*`;
+ string:RegExp expires = re `.*`;
+ test:assertTrue(envelopeString.includesMatch(ts_token));
+ test:assertTrue(envelopeString.includesMatch(created));
+ test:assertTrue(envelopeString.includesMatch(expires));
+}
+
+function assertUsernameToken(string envelopeString, PasswordType passwordType) {
+ string:RegExp usernameTokenTag = re `.*`;
+ string:RegExp usernameTag = re `${USERNAME}`;
+ test:assertTrue(envelopeString.includesMatch(usernameTokenTag));
+ test:assertTrue(envelopeString.includesMatch(usernameTag));
+ match passwordType {
+ TEXT => {
+ string:RegExp passwordTag = re `${PASSWORD}`;
+ test:assertTrue(envelopeString.includesMatch(passwordTag));
+ }
+ DIGEST => {
+ string:RegExp passwordTag = re `.*`;
+ string:RegExp nonce = re `.*`;
+ string:RegExp created = re `.*`;
+ test:assertTrue(envelopeString.includesMatch(passwordTag));
+ test:assertTrue(envelopeString.includesMatch(nonce));
+ test:assertTrue(envelopeString.includesMatch(created));
+ }
+ _ => {
+ string:RegExp salt = re `.*`;
+ string:RegExp iteration = re `.*`;
+ test:assertTrue(envelopeString.includesMatch(salt));
+ test:assertTrue(envelopeString.includesMatch(iteration));
+ }
+ }
+}
+
+function assertSignatureWithX509(string securedEnvelope) {
+ string:RegExp keyIdentifier = re `.*`;
+ test:assertTrue(securedEnvelope.includesMatch(keyIdentifier));
+ assertSignatureWithoutX509(securedEnvelope);
+}
+
+function assertSignatureWithoutX509(string securedEnvelope) {
+ string:RegExp signature = re `.*`;
+ string:RegExp signatureInfo = re `.*`;
+ string:RegExp canonicalizationMethod = re ``;
+ string:RegExp signatureMethod = re ``;
+ string:RegExp transformMethod = re ``;
+ string:RegExp digestMethod = re ``;
+ string:RegExp signatureValue = re `.*`;
+
+ test:assertTrue(securedEnvelope.includesMatch(signature));
+ test:assertTrue(securedEnvelope.includesMatch(signatureInfo));
+ test:assertTrue(securedEnvelope.includesMatch(canonicalizationMethod));
+ test:assertTrue(securedEnvelope.includesMatch(signatureMethod));
+ test:assertTrue(securedEnvelope.includesMatch(transformMethod));
+ test:assertTrue(securedEnvelope.includesMatch(digestMethod));
+ test:assertTrue(securedEnvelope.includesMatch(signatureValue));
+}
+
+function assertEncryptedSymmetricKey(string securedEnvelope) {
+ string:RegExp encryptedKey = re `.*`;
+ string:RegExp encryptionMethod = re ``;
+ string:RegExp keyInfo = re ``;
+ string:RegExp cipherData = re `.*`;
+
+ test:assertTrue(securedEnvelope.includesMatch(encryptedKey));
+ test:assertTrue(securedEnvelope.includesMatch(encryptionMethod));
+ test:assertTrue(securedEnvelope.includesMatch(keyInfo));
+ test:assertTrue(securedEnvelope.includesMatch(cipherData));
+}
+
+function assertEncryptedPart(string securedEnvelope) {
+ string:RegExp encryptedData = re ``;
+ string:RegExp keyInfo = re ``;
+ string:RegExp cipherData = re `.*`;
+ string:RegExp cipherValue = re `.*`;
+
+ test:assertTrue(securedEnvelope.includesMatch(encryptedData));
+ test:assertTrue(securedEnvelope.includesMatch(encMethod));
+ test:assertTrue(securedEnvelope.includesMatch(keyInfo));
+ test:assertTrue(securedEnvelope.includesMatch(cipherData));
+ test:assertTrue(securedEnvelope.includesMatch(cipherValue));
+}
diff --git a/ballerina/modules/wssec/tests/ws_security_tests.bal b/ballerina/modules/wssec/tests/ws_security_tests.bal
new file mode 100644
index 0000000..ffd3145
--- /dev/null
+++ b/ballerina/modules/wssec/tests/ws_security_tests.bal
@@ -0,0 +1,821 @@
+// Copyright (c) 2023, WSO2 LLC. (http://www.wso2.com) All Rights Reserved.
+//
+// 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/crypto;
+import ballerina/test;
+import ballerina/lang.regexp;
+
+@test:Config {
+ groups: ["timestamp_token"]
+}
+function testTimestampToken() returns error? {
+ xml envelope =
+ xml `
+
+
+ John Doe
+ 30
+
+ New York
+ USA
+
+
+
+ `;
+
+ xmlns "http://schemas.xmlsoap.org/soap/envelope/" as soap;
+ xml securedEnvelope = check applyTimestampToken(envelope = envelope, timeToLive = 600);
+ string envelopeString = (securedEnvelope//*).toString();
+ assertTimestampToken(envelopeString);
+}
+
+@test:Config {
+ groups: ["timestamp_token", "error"]
+}
+function testTimestampTokenWithIncorrectTimeError() returns error? {
+ xml envelope =
+ xml `
+
+
+ John Doe
+ 30
+
+ New York
+ USA
+
+
+
+ `;
+ xmlns "http://schemas.xmlsoap.org/soap/envelope/" as soap;
+ TimestampTokenConfig tsRecord = {
+ timeToLive: -1
+ };
+ xml|Error generateEnvelope = applyTimestampToken(envelope, tsRecord);
+ test:assertTrue(generateEnvelope is Error);
+ if generateEnvelope is Error {
+ test:assertEquals(generateEnvelope.message(), "Invalid value for `timeToLive`");
+ }
+}
+
+@test:Config {
+ groups: ["username_token", "password_text"]
+}
+function testUsernameTokenWithPlaintextPassword() returns error? {
+ xml envelope =
+ xml `
+
+
+ John Doe
+ 30
+
+ New York
+ USA
+
+
+
+ `;
+ xmlns "http://schemas.xmlsoap.org/soap/envelope/" as soap;
+
+ UsernameTokenConfig utRecord = {
+ username: USERNAME,
+ password: PASSWORD,
+ passwordType: TEXT
+ };
+ xml securedEnvelope = check applyUsernameToken(envelope, utRecord);
+ string envelopeString = securedEnvelope.toString();
+ assertUsernameToken(envelopeString, TEXT);
+}
+
+@test:Config {
+ groups: ["username_token", "password_text", "derived_key"]
+}
+function testUsernameTokenWithPlaintextPasswordWithDerivedKey() returns error? {
+ xml envelope =
+ xml `
+
+
+ John Doe
+ 30
+
+ New York
+ USA
+
+
+
+ `;
+ xmlns "http://schemas.xmlsoap.org/soap/envelope/" as soap;
+ UsernameTokenConfig utRecord = {
+ username: USERNAME,
+ password: PASSWORD,
+ passwordType: DERIVED_KEY_TEXT
+ };
+ xml securedEnvelope = check applyUsernameToken(envelope, utRecord);
+ string envelopeString = securedEnvelope.toString();
+
+ assertUsernameToken(envelopeString, DERIVED_KEY_TEXT);
+}
+
+@test:Config {
+ groups: ["username_token", "password_digest"]
+}
+function testUsernameTokenWithHashedPasword() returns error? {
+ xml envelope =
+ xml `
+
+
+ John Doe
+ 30
+
+ New York
+ USA
+
+
+
+ `;
+ xmlns "http://schemas.xmlsoap.org/soap/envelope/" as soap;
+ UsernameTokenConfig utRecord = {
+ username: USERNAME,
+ password: PASSWORD,
+ passwordType: DIGEST
+ };
+ xml securedEnvelope = check applyUsernameToken(envelope, utRecord);
+ string envelopeString = securedEnvelope.toString();
+
+ assertUsernameToken(envelopeString, DIGEST);
+}
+
+@test:Config {
+ groups: ["username_token", "password_digest", "derived_key"]
+}
+function testUsernameTokenWithHashedPaswordWithDerivedKey() returns error? {
+ xml envelope =
+ xml `
+
+
+ John Doe
+ 30
+
+ New York
+ USA
+
+
+
+ `;
+ xmlns "http://schemas.xmlsoap.org/soap/envelope/" as soap;
+ UsernameTokenConfig utRecord = {
+ username: USERNAME,
+ password: PASSWORD,
+ passwordType: DERIVED_KEY_DIGEST
+ };
+ xml securedEnvelope = check applyUsernameToken(envelope, utRecord);
+ string envelopeString = securedEnvelope.toString();
+
+ assertUsernameToken(envelopeString, DERIVED_KEY_DIGEST);
+}
+
+@test:Config {
+ groups: ["username_token", "signature", "symmetric_binding"]
+}
+function testSymmetricBindingPolicyWithSignatureOnly() returns error? {
+ xml envelope =
+ xml `
+
+
+ John Doe
+ 30
+
+ New York
+ USA
+
+
+
+ `;
+ xmlns "http://schemas.xmlsoap.org/soap/envelope/" as soap;
+
+ SymmetricBindingConfig symmetricBinding = {
+ signatureAlgorithm: RSA_SHA256,
+ symmetricKey: symmetricKey,
+ servicePublicKey: serverPublicKey
+ };
+
+ xml securedEnvelope = check applySymmetricBinding(envelope, symmetricBinding);
+ string envelopeString = securedEnvelope.toString();
+ byte[] signedData = check getSignatureData(securedEnvelope);
+
+ boolean validity = check crypto:verifyRsaSha256Signature((envelope//*).toString().toBytes(),
+ signedData, publicKey);
+ test:assertTrue(validity);
+
+ assertEncryptedSymmetricKey(envelopeString);
+ assertSignatureWithoutX509(envelopeString);
+}
+
+@test:Config {
+ groups: ["username_token", "signature", "symmetric_binding"]
+}
+function testSymmetricBindingPolicyEncryptionOnly() returns error? {
+ xml envelope =
+ xml `
+
+ `;
+ xmlns "http://schemas.xmlsoap.org/soap/envelope/" as soap;
+
+ SymmetricBindingConfig symmetricBinding = {
+ encryptionAlgorithm: RSA_ECB,
+ symmetricKey: symmetricKey,
+ servicePublicKey: serverPublicKey
+ };
+
+ xml securedEnvelope = check applySymmetricBinding(envelope, symmetricBinding);
+ string envelopeString = securedEnvelope.toString();
+
+ byte[] encData = check getEncryptedData(securedEnvelope);
+ byte[] decryptDataResult = check crypto:decryptRsaEcb(encData, publicKey);
+ test:assertEquals((envelope//*).toString(), check string:fromBytes(decryptDataResult));
+
+ assertEncryptedSymmetricKey(envelopeString);
+ assertEncryptedPart(envelopeString);
+}
+
+@test:Config {
+ groups: ["username_token", "signature", "symmetric_binding"]
+}
+function testSymmetricBindingWithSignatureAndEncryption() returns error? {
+ xml envelope =
+ xml `
+
+ `;
+ xmlns "http://schemas.xmlsoap.org/soap/envelope/" as soap;
+
+ SymmetricBindingConfig symmetricBinding = {
+ signatureAlgorithm: RSA_SHA256,
+ encryptionAlgorithm: RSA_ECB,
+ symmetricKey: symmetricKey,
+ servicePublicKey: serverPublicKey
+ };
+ xml securedEnvelope = check applySymmetricBinding(envelope, symmetricBinding);
+ string envelopeString = securedEnvelope.toString();
+
+ byte[] signedData = check getSignatureData(securedEnvelope);
+
+ boolean validity = check crypto:verifyRsaSha256Signature((envelope//*).toString().toBytes(),
+ signedData, publicKey);
+ test:assertTrue(validity);
+
+ byte[] encData = check getEncryptedData(securedEnvelope);
+ byte[] decryptDataResult = check crypto:decryptRsaEcb(encData, publicKey);
+ test:assertEquals((envelope//*).toString(), check string:fromBytes(decryptDataResult));
+
+ assertEncryptedSymmetricKey(envelopeString);
+ assertSignatureWithoutX509(envelopeString);
+ assertEncryptedPart(envelopeString);
+}
+
+@test:Config {
+ groups: ["username_token", "signature", "symmetric_binding"]
+}
+function testSymmetricBindingPolicyWithX509SignatureAndEncryption() returns error? {
+ xml envelope =
+ xml `
+
+ `;
+ xmlns "http://schemas.xmlsoap.org/soap/envelope/" as soap;
+
+ SymmetricBindingConfig symmetricBinding = {
+ signatureAlgorithm: RSA_SHA256,
+ encryptionAlgorithm: RSA_ECB,
+ symmetricKey: symmetricKey,
+ servicePublicKey: serverPublicKey,
+ x509Token: X509_PUBLIC_CERT_PATH_2
+ };
+
+ xml securedEnvelope = check applySymmetricBinding(envelope, symmetricBinding);
+ string envelopeString = securedEnvelope.toString();
+
+ byte[] signedData = check getSignatureData(securedEnvelope);
+
+ boolean validity = check crypto:verifyRsaSha256Signature((envelope//*).toString().toBytes(),
+ signedData, publicKey);
+ test:assertTrue(validity);
+
+ byte[] encData = check getEncryptedData(securedEnvelope);
+ byte[] decryptDataResult = check crypto:decryptRsaEcb(encData, publicKey);
+ test:assertEquals((envelope//*).toString(), check string:fromBytes(decryptDataResult));
+
+ assertEncryptedSymmetricKey(envelopeString);
+ assertSignatureWithX509(envelopeString);
+ assertEncryptedPart(envelopeString);
+}
+
+@test:Config {
+ groups: ["username_token", "signature", "symmetric_binding"]
+}
+function testUsernameTokenWithSymmetricBinding() returns error? {
+ xml envelope =
+ xml `
+
+ `;
+ xmlns "http://schemas.xmlsoap.org/soap/envelope/" as soap;
+
+ UsernameTokenConfig utRecord = {
+ username: USERNAME,
+ password: PASSWORD,
+ passwordType: DIGEST
+ };
+
+ envelope = check applyUsernameToken(envelope, utRecord);
+
+ SymmetricBindingConfig symmetricBinding = {
+ signatureAlgorithm: RSA_SHA256,
+ encryptionAlgorithm: RSA_ECB,
+ symmetricKey: symmetricKey,
+ servicePublicKey: serverPublicKey
+ };
+ xml securedEnvelope = check applySymmetricBinding(envelope, symmetricBinding);
+ string envelopeString = securedEnvelope.toString();
+
+ byte[] signedData = check getSignatureData(securedEnvelope);
+
+ boolean validity = check crypto:verifyRsaSha256Signature((envelope//*).toString().toBytes(),
+ signedData, publicKey);
+ test:assertTrue(validity);
+
+ byte[] encData = check getEncryptedData(securedEnvelope);
+ byte[] decryptDataResult = check crypto:decryptRsaEcb(encData, publicKey);
+ test:assertEquals((envelope//*).toString(), check string:fromBytes(decryptDataResult));
+
+ assertEncryptedSymmetricKey(envelopeString);
+ assertUsernameToken(envelopeString, DIGEST);
+ assertSignatureWithoutX509(envelopeString);
+ assertEncryptedPart(envelopeString);
+}
+
+@test:Config {
+ groups: ["username_token", "signature", "symmetric_binding"]
+}
+function testUsernameTokenTimestampWithSymmetricBindingAndX509Token() returns error? {
+ xml envelope =
+ xml `
+
+ `;
+ xmlns "http://schemas.xmlsoap.org/soap/envelope/" as soap;
+
+ UsernameTokenConfig utRecord = {
+ username: USERNAME,
+ password: PASSWORD,
+ passwordType: DIGEST
+ };
+
+ envelope = check applyUsernameToken(envelope, utRecord);
+ envelope = check applyTimestampToken(envelope = envelope, timeToLive = 600);
+
+ crypto:KeyStore serverKeyStore = {
+ path: X509_KEY_STORE_PATH,
+ password: KEY_PASSWORD
+ };
+ crypto:PublicKey serverPublicKey = check crypto:decodeRsaPublicKeyFromTrustStore(serverKeyStore, KEY_ALIAS);
+
+ crypto:KeyStore keyStore = {
+ path: KEY_STORE_PATH,
+ password: KEY_PASSWORD
+ };
+ crypto:PrivateKey symmetricKey = check crypto:decodeRsaPrivateKeyFromKeyStore(keyStore, KEY_ALIAS, KEY_PASSWORD);
+ crypto:PublicKey publicKey = check crypto:decodeRsaPublicKeyFromTrustStore(keyStore, KEY_ALIAS);
+
+ SymmetricBindingConfig symmetricBinding = {
+ signatureAlgorithm: RSA_SHA256,
+ encryptionAlgorithm: RSA_ECB,
+ symmetricKey: symmetricKey,
+ servicePublicKey: serverPublicKey,
+ x509Token: X509_PUBLIC_CERT_PATH_2
+ };
+
+ xml securedEnvelope = check applySymmetricBinding(envelope, symmetricBinding);
+ string envelopeString = securedEnvelope.toString();
+ byte[] signedData = check getSignatureData(securedEnvelope);
+
+ boolean validity = check crypto:verifyRsaSha256Signature((envelope//*).toString().toBytes(),
+ signedData, publicKey);
+ test:assertTrue(validity);
+
+ byte[] encData = check getEncryptedData(securedEnvelope);
+ byte[] decryptDataResult = check crypto:decryptRsaEcb(encData, publicKey);
+ test:assertEquals((envelope//*).toString(), check string:fromBytes(decryptDataResult));
+
+ assertEncryptedSymmetricKey(envelopeString);
+ assertUsernameToken(envelopeString, DIGEST);
+ assertTimestampToken(envelopeString);
+ assertSignatureWithoutX509(envelopeString);
+ assertEncryptedPart(envelopeString);
+}
+
+@test:Config {
+ groups: ["username_token", "signature", "symmetric_binding", "outbound_config"]
+}
+function testSymmetricBindingWithOutboundConfig() returns error? {
+ xml envelope =
+ xml `
+
+ `;
+ xmlns "http://schemas.xmlsoap.org/soap/envelope/" as soap;
+
+ crypto:KeyStore serverKeyStore = {
+ path: X509_KEY_STORE_PATH,
+ password: KEY_PASSWORD
+ };
+ crypto:PublicKey serverPublicKey = check crypto:decodeRsaPublicKeyFromTrustStore(serverKeyStore, KEY_ALIAS);
+
+ crypto:KeyStore keyStore = {
+ path: KEY_STORE_PATH,
+ password: KEY_PASSWORD
+ };
+ crypto:PrivateKey symmetricKey = check crypto:decodeRsaPrivateKeyFromKeyStore(keyStore, KEY_ALIAS, KEY_PASSWORD);
+ crypto:PublicKey publicKey = check crypto:decodeRsaPublicKeyFromTrustStore(keyStore, KEY_ALIAS);
+
+ SymmetricBindingConfig symmetricBinding = {
+ signatureAlgorithm: RSA_SHA256,
+ encryptionAlgorithm: RSA_ECB,
+ symmetricKey: symmetricKey,
+ servicePublicKey: serverPublicKey
+ };
+
+ xml securedEnvelope = check applySymmetricBinding(envelope, symmetricBinding);
+ string envelopeString = securedEnvelope.toString();
+
+ OutboundSecurityConfig outboundConfig = {
+ verificationKey: publicKey,
+ signatureAlgorithm: RSA_SHA256,
+ decryptionAlgorithm: RSA_ECB,
+ decryptionKey: publicKey
+ };
+
+ crypto:PrivateKey|crypto:PublicKey? privateKey = outboundConfig.decryptionKey;
+ if privateKey is crypto:PrivateKey|crypto:PublicKey {
+ byte[] encData = check getEncryptedData(securedEnvelope);
+ byte[] decryptDataResult = check crypto:decryptRsaEcb(encData, privateKey);
+ string decryptedBody = "" + check string:fromBytes(decryptDataResult) + "";
+ envelopeString = regexp:replace(re `.*`, envelopeString, decryptedBody);
+ securedEnvelope = check xml:fromString(envelopeString);
+ }
+ byte[] signedData = check getSignatureData(securedEnvelope);
+
+ boolean validity = check crypto:verifyRsaSha256Signature((envelope//*).toString().toBytes(),
+ signedData, publicKey);
+ test:assertTrue(validity);
+}
+
+@test:Config {
+ groups: ["username_token", "signature", "asymmetric_binding"]
+}
+function testAsymmetricBindingWithSignatureRsaSha256() returns error? {
+ xml envelope =
+ xml `
+
+ `;
+ xmlns "http://schemas.xmlsoap.org/soap/envelope/" as soap;
+
+ AsymmetricBindingConfig asymmetricBinding = {
+ signatureAlgorithm: RSA_SHA256,
+ signatureKey: clientPrivateKey,
+ encryptionKey: serverPublicKey
+ };
+ xml securedEnvelope = check applyAsymmetricBinding(envelope, asymmetricBinding);
+ string envelopeString = securedEnvelope.toString();
+ byte[] signedData = check getSignatureData(securedEnvelope);
+ Error? validity = check verifyData((envelope//*).toString().toBytes(), signedData,
+ clientPublicKey, RSA_SHA256);
+ test:assertTrue(validity is ());
+
+ assertSignatureWithoutX509(envelopeString);
+}
+
+@test:Config {
+ groups: ["username_token", "signature", "asymmetric_binding"]
+}
+function testAsymmetricBindingWithX509Signature() returns error? {
+ xml envelope =
+ xml `
+
+ `;
+ xmlns "http://schemas.xmlsoap.org/soap/envelope/" as soap;
+
+ AsymmetricBindingConfig asymmetricBinding = {
+ signatureAlgorithm: RSA_SHA256,
+ signatureKey: clientPrivateKey,
+ encryptionKey: serverPublicKey,
+ x509Token: X509_PUBLIC_CERT_PATH_2
+ };
+ xml securedEnvelope = check applyAsymmetricBinding(envelope, asymmetricBinding);
+ string envelopeString = securedEnvelope.toString();
+
+ byte[] signedData = check getSignatureData(securedEnvelope);
+ boolean validity = check crypto:verifyRsaSha256Signature((envelope//*).toString().toBytes(),
+ signedData, clientPublicKey);
+ test:assertTrue(validity);
+
+ assertSignatureWithX509(envelopeString);
+}
+
+@test:Config {
+ groups: ["username_token", "signature", "asymmetric_binding"]
+}
+function testAsymmetricBindingWithEncryption() returns error? {
+ xml envelope =
+ xml `
+
+ `;
+ xmlns "http://schemas.xmlsoap.org/soap/envelope/" as soap;
+
+ AsymmetricBindingConfig asymmetricBinding = {
+ encryptionAlgorithm: RSA_ECB,
+ signatureKey: clientPrivateKey,
+ encryptionKey: serverPublicKey
+ };
+ xml securedEnvelope = check applyAsymmetricBinding(envelope, asymmetricBinding);
+ string envelopeString = securedEnvelope.toString();
+
+ byte[] encData = check getEncryptedData(securedEnvelope);
+ byte[] decryptDataResult = check crypto:decryptRsaEcb(encData, serverPrivateKey);
+ test:assertEquals(check string:fromBytes(decryptDataResult), (envelope//*).toString());
+
+ assertEncryptedPart(envelopeString);
+}
+
+@test:Config {
+ groups: ["username_token", "signature", "asymmetric_binding", "rr"]
+}
+function testAsymmetricBindingWithSignatureAndEncryption() returns error? {
+ xml envelope =
+ xml `
+ John Doe
+ `;
+ xmlns "http://schemas.xmlsoap.org/soap/envelope/" as soap;
+
+ AsymmetricBindingConfig asymmetricBinding = {
+ signatureAlgorithm: RSA_SHA256,
+ encryptionAlgorithm: RSA_ECB,
+ signatureKey: clientPrivateKey,
+ encryptionKey: serverPublicKey
+ };
+
+ xml securedEnvelope = check applyAsymmetricBinding(envelope, asymmetricBinding);
+ string envelopeString = securedEnvelope.toString();
+
+ byte[] signedData = check getSignatureData(securedEnvelope);
+ boolean validity = check crypto:verifyRsaSha256Signature((envelope//*).toString().toBytes(),
+ signedData, clientPublicKey);
+ test:assertTrue(validity);
+
+ byte[] encData = check getEncryptedData(securedEnvelope);
+ byte[] decryptDataResult = check crypto:decryptRsaEcb(encData, serverPrivateKey);
+ test:assertEquals(check string:fromBytes(decryptDataResult), (envelope//*).toString());
+
+ assertSignatureWithoutX509(envelopeString);
+ assertEncryptedPart(envelopeString);
+}
+
+@test:Config {
+ groups: ["username_token", "signature", "asymmetric_binding"]
+}
+function testAsymmetricBindingWithX509SignatureAndEncryption() returns error? {
+ xml envelope =
+ xml `
+
+ `;
+ xmlns "http://schemas.xmlsoap.org/soap/envelope/" as soap;
+
+ AsymmetricBindingConfig asymmetricBinding = {
+ signatureAlgorithm: RSA_SHA256,
+ encryptionAlgorithm: RSA_ECB,
+ signatureKey: clientPrivateKey,
+ encryptionKey: serverPublicKey,
+ x509Token: X509_PUBLIC_CERT_PATH_2
+ };
+ xml securedEnvelope = check applyAsymmetricBinding(envelope, asymmetricBinding);
+ string envelopeString = securedEnvelope.toString();
+
+ byte[] signedData = check getSignatureData(securedEnvelope);
+ boolean validity = check crypto:verifyRsaSha256Signature((envelope//*).toString().toBytes(),
+ signedData, clientPublicKey);
+ test:assertTrue(validity);
+
+ byte[] encData = check getEncryptedData(securedEnvelope);
+ byte[] decryptDataResult = check crypto:decryptRsaEcb(encData, serverPrivateKey);
+ test:assertEquals((envelope//*).toString(), check string:fromBytes(decryptDataResult));
+
+ assertSignatureWithX509(envelopeString);
+ assertEncryptedPart(envelopeString);
+}
+
+@test:Config {
+ groups: ["username_token", "signature", "asymmetric_binding"]
+}
+function testUsernameTokenWithAsymmetricBindingAndX509() returns error? {
+ xml envelope =
+ xml `
+
+ `;
+ xmlns "http://schemas.xmlsoap.org/soap/envelope/" as soap;
+
+ UsernameTokenConfig utRecord = {
+ username: USERNAME,
+ password: PASSWORD,
+ passwordType: DIGEST
+ };
+ envelope = check applyUsernameToken(envelope, utRecord);
+
+ AsymmetricBindingConfig asymmetricBinding = {
+ signatureAlgorithm: RSA_SHA256,
+ encryptionAlgorithm: RSA_ECB,
+ signatureKey: clientPrivateKey,
+ encryptionKey: serverPublicKey,
+ x509Token: X509_PUBLIC_CERT_PATH_2
+ };
+ xml securedEnvelope = check applyAsymmetricBinding(envelope, asymmetricBinding);
+ string envelopeString = securedEnvelope.toString();
+
+ byte[] signedData = check getSignatureData(securedEnvelope);
+ boolean validity = check crypto:verifyRsaSha256Signature((envelope//*).toString().toBytes(),
+ signedData, clientPublicKey);
+ test:assertTrue(validity);
+
+ byte[] encData = check getEncryptedData(securedEnvelope);
+ byte[] decryptDataResult = check crypto:decryptRsaEcb(encData, serverPrivateKey);
+ test:assertEquals((envelope//*).toString(), check string:fromBytes(decryptDataResult));
+
+ assertUsernameToken(envelopeString, DIGEST);
+ assertSignatureWithX509(envelopeString);
+ assertEncryptedPart(envelopeString);
+}
+
+@test:Config {
+ groups: ["username_token", "signature", "asymmetric_binding"]
+}
+function testUsernameTokenTimestampWithAsymmetricBindingAndX509() returns error? {
+ xml envelope =
+ xml `
+
+ `;
+ xmlns "http://schemas.xmlsoap.org/soap/envelope/" as soap;
+
+ UsernameTokenConfig utRecord = {
+ username: USERNAME,
+ password: PASSWORD,
+ passwordType: DIGEST
+ };
+ envelope = check applyUsernameToken(envelope, utRecord);
+ envelope = check applyTimestampToken(envelope = envelope, timeToLive = 600);
+
+ AsymmetricBindingConfig asymmetricBinding = {
+ signatureAlgorithm: RSA_SHA256,
+ encryptionAlgorithm: RSA_ECB,
+ signatureKey: clientPrivateKey,
+ encryptionKey: serverPublicKey,
+ x509Token: X509_PUBLIC_CERT_PATH_2
+ };
+ xml securedEnvelope = check applyAsymmetricBinding(envelope, asymmetricBinding);
+ string envelopeString = securedEnvelope.toString();
+
+ byte[] signedData = check getSignatureData(securedEnvelope);
+ boolean validity = check crypto:verifyRsaSha256Signature((envelope//*).toString().toBytes(),
+ signedData, clientPublicKey);
+ test:assertTrue(validity);
+
+ byte[] encData = check getEncryptedData(securedEnvelope);
+ byte[] decryptDataResult = check crypto:decryptRsaEcb(encData, serverPrivateKey);
+ test:assertEquals((envelope//*).toString(), check string:fromBytes(decryptDataResult));
+
+ assertUsernameToken(envelopeString, DIGEST);
+ assertTimestampToken(envelopeString);
+ assertSignatureWithX509(envelopeString);
+ assertEncryptedPart(envelopeString);
+}
+
+
+@test:Config {
+ groups: ["username_token", "signature", "asymmetric_binding", "outbound_config"]
+}
+function testAsymmetricBindingWithOutboundConfig() returns error? {
+ xml envelope =
+ xml `
+ John Doe
+ `;
+ xmlns "http://schemas.xmlsoap.org/soap/envelope/" as soap;
+
+ AsymmetricBindingConfig asymmetricBinding = {
+ signatureAlgorithm: RSA_SHA256,
+ encryptionAlgorithm: RSA_ECB,
+ signatureKey: clientPrivateKey,
+ encryptionKey: serverPublicKey
+ };
+
+ OutboundSecurityConfig outboundConfig = {
+ verificationKey: clientPublicKey,
+ signatureAlgorithm: RSA_SHA256,
+ decryptionAlgorithm: RSA_ECB,
+ decryptionKey: serverPrivateKey
+ };
+
+ xml securedEnvelope = check applyAsymmetricBinding(envelope, asymmetricBinding);
+ string envelopeString = securedEnvelope.toString();
+ crypto:PrivateKey|crypto:PublicKey? privateKey = outboundConfig.decryptionKey;
+ if privateKey is crypto:PrivateKey|crypto:PublicKey {
+ byte[] encData = check getEncryptedData(securedEnvelope);
+ byte[] decryptDataResult = check crypto:decryptRsaEcb(encData, privateKey);
+ string decryptedBody = "" + check string:fromBytes(decryptDataResult) + "";
+ envelopeString = regexp:replace(re `.*`, envelopeString, decryptedBody);
+ securedEnvelope = check xml:fromString(envelopeString);
+ }
+ byte[] signedData = check getSignatureData(securedEnvelope);
+ boolean validity = check crypto:verifyRsaSha256Signature((envelope//*).toString().toBytes(),
+ signedData, clientPublicKey);
+ test:assertTrue(validity);
+}
+
+@test:Config {
+ groups: ["username_token", "signature", "asymmetric_binding"]
+}
+function testAsymmetricBindingWithSignatureWithRsaSha1() returns error? {
+ xml envelope =
+ xml `
+
+ `;
+ xmlns "http://schemas.xmlsoap.org/soap/envelope/" as soap;
+
+ AsymmetricBindingConfig asymmetricBinding = {
+ signatureAlgorithm: RSA_SHA1,
+ signatureKey: clientPrivateKey,
+ encryptionKey: serverPublicKey
+ };
+ xml securedEnvelope = check applyAsymmetricBinding(envelope, asymmetricBinding);
+ string envelopeString = securedEnvelope.toString();
+ byte[] signedData = check getSignatureData(securedEnvelope);
+ Error? validity = check verifyData((envelope//*).toString().toBytes(), signedData,
+ clientPublicKey, RSA_SHA1);
+ test:assertTrue(validity is ());
+
+ assertSignatureWithoutX509(envelopeString);
+}
+
+@test:Config {
+ groups: ["username_token", "signature", "asymmetric_binding"]
+}
+function testAsymmetricBindingWithSignatureWithRsaSha384() returns error? {
+ xml envelope =
+ xml `
+
+ `;
+ xmlns "http://schemas.xmlsoap.org/soap/envelope/" as soap;
+
+ AsymmetricBindingConfig asymmetricBinding = {
+ signatureAlgorithm: RSA_SHA384,
+ signatureKey: clientPrivateKey,
+ encryptionKey: serverPublicKey
+ };
+ xml securedEnvelope = check applyAsymmetricBinding(envelope, asymmetricBinding);
+ string envelopeString = securedEnvelope.toString();
+ byte[] signedData = check getSignatureData(securedEnvelope);
+ Error? validity = check verifyData((envelope//*).toString().toBytes(), signedData,
+ clientPublicKey, RSA_SHA384);
+ test:assertTrue(validity is ());
+
+ assertSignatureWithoutX509(envelopeString);
+}
+
+@test:Config {
+ groups: ["username_token", "signature", "asymmetric_binding"]
+}
+function testAsymmetricBindingWithSignatureWithRsaSha512() returns error? {
+ xml envelope =
+ xml `
+
+ `;
+ xmlns "http://schemas.xmlsoap.org/soap/envelope/" as soap;
+
+ AsymmetricBindingConfig asymmetricBinding = {
+ signatureAlgorithm: RSA_SHA512,
+ signatureKey: clientPrivateKey,
+ encryptionKey: serverPublicKey
+ };
+ xml securedEnvelope = check applyAsymmetricBinding(envelope, asymmetricBinding);
+ string envelopeString = securedEnvelope.toString();
+ byte[] signedData = check getSignatureData(securedEnvelope);
+ Error? validity = check verifyData((envelope//*).toString().toBytes(), signedData,
+ clientPublicKey, RSA_SHA512);
+ test:assertTrue(validity is ());
+
+ assertSignatureWithoutX509(envelopeString);
+}
diff --git a/ballerina/modules/wssec/types.bal b/ballerina/modules/wssec/types.bal
new file mode 100644
index 0000000..6767a51
--- /dev/null
+++ b/ballerina/modules/wssec/types.bal
@@ -0,0 +1,33 @@
+// Copyright (c) 2023, WSO2 LLC. (http://www.wso2.com) All Rights Reserved.
+//
+// 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 enum PasswordType {
+ TEXT,
+ DIGEST,
+ DERIVED_KEY_TEXT,
+ DERIVED_KEY_DIGEST
+}
+
+public enum SignatureAlgorithm {
+ RSA_SHA1 = "http://www.w3.org/2000/09/xmldsig#rsa-sha1",
+ RSA_SHA256 = "http://www.w3.org/2001/04/xmldsig-more#rsa-sha256",
+ RSA_SHA384 = "http://www.w3.org/2001/04/xmldsig-more#rsa-sha384",
+ RSA_SHA512 = "http://www.w3.org/2001/04/xmldsig-more#rsa-sha512"
+}
+
+public enum EncryptionAlgorithm {
+ RSA_ECB = "http://www.w3.org/2001/04/xmlenc#rsa-1_5"
+}
diff --git a/ballerina/modules/wssec/ws_security.bal b/ballerina/modules/wssec/ws_security.bal
new file mode 100644
index 0000000..1b570db
--- /dev/null
+++ b/ballerina/modules/wssec/ws_security.bal
@@ -0,0 +1,39 @@
+// Copyright (c) 2023, WSO2 LLC. (http://www.wso2.com) All Rights Reserved.
+//
+// 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;
+
+isolated class WsSecurity {
+ isolated function applyUsernameTokenPolicy(WSSecurityHeader wsSecurityHeader, string username,
+ string password, string pwType) returns string|Error = @java:Method {
+ 'class: "org.wssec.WsSecurity"
+ } external;
+
+ isolated function applyTimestampPolicy(WSSecurityHeader wsSecurityHeader, int timeToLive)
+ returns string|Error = @java:Method {
+ 'class: "org.wssec.WsSecurity"
+ } external;
+
+ isolated function applySignatureOnlyPolicy(WSSecurityHeader wsSecurityPolicy, Signature signature, string? x509FilePath)
+ returns string|Error = @java:Method {
+ 'class: "org.wssec.WsSecurity"
+ } external;
+
+ isolated function applyEncryptionOnlyPolicy(WSSecurityHeader wsSecurityPolicy, Encryption encryption)
+ returns string|Error = @java:Method {
+ 'class: "org.wssec.WsSecurity"
+ } external;
+}
diff --git a/ballerina/modules/wssec/ws_security_methods.bal b/ballerina/modules/wssec/ws_security_methods.bal
new file mode 100644
index 0000000..9e328d0
--- /dev/null
+++ b/ballerina/modules/wssec/ws_security_methods.bal
@@ -0,0 +1,199 @@
+// Copyright (c) 2023, WSO2 LLC. (http://www.wso2.com) All Rights Reserved.
+//
+// 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/crypto;
+import ballerina/lang.regexp;
+
+xmlns "http://schemas.xmlsoap.org/soap/envelope/" as soap;
+xmlns "http://www.w3.org/2000/09/xmldsig#" as ds;
+
+isolated function addSecurityHeader(Document document) returns WSSecurityHeader|Error {
+ WSSecurityHeader wsSecHeader = check new (document);
+ Error? insertHeader = wsSecHeader.insertSecHeader();
+ return insertHeader ?: wsSecHeader;
+}
+
+public isolated function verifyData(byte[] data, byte[] signature, crypto:PublicKey publicKey,
+ SignatureAlgorithm signatureAlgorithm) returns Error? {
+ Signature sign = check new ();
+ boolean verifySignature = check sign.verifySignature(data, signature, publicKey, signatureAlgorithm);
+ if !verifySignature {
+ return error Error("Signature verification of the SOAP envelope has been failed");
+ }
+}
+
+isolated function addSignature(Signature sign, string signatureAlgorithm, byte[] signature) returns Signature|Error {
+ sign.setSignatureAlgorithm(signatureAlgorithm);
+ sign.setSignatureValue(signature);
+ return sign;
+}
+
+isolated function addEncryption(Encryption encrypt, string encryptionAlgorithm, byte[] encryption) returns Encryption|Error {
+ encrypt.setEncryptionAlgorithm(encryptionAlgorithm);
+ encrypt.setEncryptedData(encryption);
+ return encrypt;
+}
+
+isolated function applyEncryptedKey(string envelopeString, crypto:PrivateKey symmetricKey, crypto:PublicKey encryptKey)
+ returns string|Error {
+ string securedEnvelope = envelopeString;
+ do {
+ Encryption encryption = check new ();
+ byte[] encryptedKey = check crypto:encryptRsaEcb(symmetricKey.toBalString().toBytes(), encryptKey);
+ string encryptedKeyElements = check encryption.getEncryptedKeyElements(encryptedKey);
+ string replace = regexp:replace(re `.*?><`, encryptedKeyElements, "<");
+ string:RegExp securityToken =
+ re ``;
+ if securedEnvelope.includesMatch(securityToken) {
+ securedEnvelope = regexp:replace(securityToken, securedEnvelope, replace);
+ }
+ else if securedEnvelope.includesMatch(re ``) {
+ securedEnvelope = regexp:replace(re ``, securedEnvelope, replace);
+ }
+ return securedEnvelope;
+ } on fail var e {
+ return error Error("Error occurred while applying the encrypted key to the envelope", e.cause());
+ }
+}
+
+isolated function convertStringToXml(string envelope) returns xml|Error {
+ xml|error xmlEnvelope = xml:fromString(regexp:replace(re `.*?><`, envelope, "<"));
+ if xmlEnvelope is error {
+ return error Error(xmlEnvelope.message());
+ }
+ return xmlEnvelope;
+}
+
+# Returns the encrypted data of the SOAP envelope.
+#
+# + envelope - The SOAP envelope
+# + return - A `byte[]` if the encrypted data is successfully decoded or else `wssec:Error`
+public isolated function getEncryptedData(xml envelope) returns byte[]|Error {
+ Document document = check new (envelope);
+ return document.getEncryptedData();
+}
+
+# Returns the signed data of the SOAP envelope.
+#
+# + envelope - The SOAP envelope
+# + return - A `byte[]` if the signed data is successfully decoded or else `wssec:Error`
+public isolated function getSignatureData(xml envelope) returns byte[]|Error {
+ Document document = check new (envelope);
+ return document.getSignatureData();
+}
+
+# Apply timestamp token security policy to the SOAP envelope.
+#
+# + envelope - The SOAP envelope
+# + timestampToken - The `TSRecord` record with the required parameters
+# + return - A `xml` type of SOAP envelope if the security binding is successfully added or else `wssec:Error`
+public isolated function applyTimestampToken(xml envelope, *TimestampTokenConfig timestampToken) returns xml|Error {
+ if timestampToken.timeToLive <= 0 {
+ return error Error("Invalid value for `timeToLive`");
+ }
+ Document document = check new (envelope);
+ WSSecurityHeader wsSecurityHeader = check addSecurityHeader(document);
+ WsSecurity wsSecurity = new;
+ string securedEnvelope = check wsSecurity.applyTimestampPolicy(wsSecurityHeader, timestampToken.timeToLive);
+ return convertStringToXml(securedEnvelope);
+}
+
+# Apply username token security policy to the SOAP envelope.
+#
+# + envelope - The SOAP envelope
+# + usernameToken - The `UsernameTokenConfig` record with the required parameters
+# + return - A `xml` type of SOAP envelope if the security binding is successfully added or else `wssec:Error`
+public isolated function applyUsernameToken(xml envelope, *UsernameTokenConfig usernameToken) returns xml|Error {
+ Document document = check new (envelope);
+ WSSecurityHeader wsSecurityHeader = check addSecurityHeader(document);
+ WsSecurity wsSecurity = new;
+ string securedEnvelope = check wsSecurity
+ .applyUsernameTokenPolicy(wsSecurityHeader, usernameToken.username, usernameToken.password,
+ usernameToken.passwordType);
+ return convertStringToXml(securedEnvelope);
+}
+
+# Apply symmetric binding security policy with username token to the SOAP envelope.
+#
+# + envelope - The SOAP envelope
+# + symmetricBinding - The `SymmetricBindingConfig` record with the required parameters
+# + return - A `xml` type of SOAP envelope if the security binding is successfully added or else `wssec:Error`
+public isolated function applySymmetricBinding(xml envelope, *SymmetricBindingConfig symmetricBinding)
+ returns xml|crypto:Error|Error {
+ Document document = check new (envelope);
+ WSSecurityHeader wsSecurityHeader = check addSecurityHeader(document);
+ string securedEnvelope = envelope.toBalString();
+ SignatureAlgorithm? signatureAlgorithm = symmetricBinding.signatureAlgorithm;
+ EncryptionAlgorithm? encryptionAlgorithm = symmetricBinding.encryptionAlgorithm;
+ if signatureAlgorithm is SignatureAlgorithm {
+ Signature signature = check new ();
+ byte[] signedData = check signature.signData((envelope//*).toString(), signatureAlgorithm
+ , symmetricBinding.symmetricKey);
+ Signature signatureResult = check addSignature(signature, signatureAlgorithm, signedData);
+ WsSecurity wsSecurity = new;
+ securedEnvelope = check wsSecurity.applySignatureOnlyPolicy(wsSecurityHeader, signatureResult,
+ symmetricBinding.x509Token);
+ }
+ if encryptionAlgorithm is EncryptionAlgorithm {
+ Encryption encryption = check new ();
+ byte[] encryptData = check crypto:encryptRsaEcb((envelope//*).toString().toBytes(),
+ symmetricBinding.symmetricKey);
+ Encryption encryptionResult = check addEncryption(encryption, encryptionAlgorithm, encryptData);
+ WsSecurity wsSecurity = new;
+ securedEnvelope = check wsSecurity.applyEncryptionOnlyPolicy(wsSecurityHeader, encryptionResult);
+ }
+ securedEnvelope = check applyEncryptedKey(securedEnvelope, symmetricBinding.symmetricKey,
+ symmetricBinding.servicePublicKey);
+ return convertStringToXml(securedEnvelope);
+}
+
+# Apply asymmetric binding security policy with X509 token to the SOAP envelope.
+#
+# + envelope - The SOAP envelope
+# + asymmetricBinding - The `AsymmetricBindingConfig` record with the required parameters
+# + return - A `xml` type of SOAP envelope if the security binding is successfully added or else `wssec:Error`
+public isolated function applyAsymmetricBinding(xml envelope, *AsymmetricBindingConfig asymmetricBinding) returns xml|crypto:Error|Error {
+ Document document = check new (envelope);
+ WSSecurityHeader wsSecurityHeader = check addSecurityHeader(document);
+ string securedEnvelope = envelope.toBalString();
+ SignatureAlgorithm? signatureAlgorithm = asymmetricBinding.signatureAlgorithm;
+ EncryptionAlgorithm? encryptionAlgorithm = asymmetricBinding.encryptionAlgorithm;
+ if signatureAlgorithm is SignatureAlgorithm {
+ Signature signature = check new ();
+ crypto:PrivateKey? signatureKey = asymmetricBinding.signatureKey;
+ if signatureKey !is crypto:PrivateKey {
+ return error Error("Signature key cannot be nil");
+ }
+ byte[] signedData = check signature.signData((envelope//*).toString(),
+ signatureAlgorithm, signatureKey);
+ Signature signatureResult = check addSignature(signature, signatureAlgorithm, signedData);
+ WsSecurity wsSecurity = new;
+ securedEnvelope = check wsSecurity.applySignatureOnlyPolicy(wsSecurityHeader, signatureResult,
+ asymmetricBinding.x509Token);
+ }
+ if encryptionAlgorithm is EncryptionAlgorithm {
+ Encryption encryption = check new ();
+ crypto:PublicKey? encryptionKey = asymmetricBinding.encryptionKey;
+ if encryptionKey !is crypto:PublicKey {
+ return error Error("Encryption key cannot be nil");
+ }
+ byte[] encryptData = check crypto:encryptRsaEcb((envelope//*).toString().toBytes(), encryptionKey);
+ Encryption encryptionResult = check addEncryption(encryption, encryptionAlgorithm, encryptData);
+ WsSecurity wsSecurity = new;
+ securedEnvelope = check wsSecurity.applyEncryptionOnlyPolicy(wsSecurityHeader, encryptionResult);
+ }
+ return convertStringToXml(securedEnvelope);
+}
diff --git a/ballerina/soap.bal b/ballerina/soap.bal
deleted file mode 100644
index 4629efa..0000000
--- a/ballerina/soap.bal
+++ /dev/null
@@ -1,81 +0,0 @@
-// Copyright (c) 2023, WSO2 LLC. (http://www.wso2.org) All Rights Reserved.
-//
-// 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/http;
-import ballerina/mime;
-
-# Defines the supported SOAP versions.
-public enum SoapVersion {
- # Represents SOAP 1.1 version
- SOAP11,
- # Represents SOAP 1.2 version
- SOAP12
-}
-
-# Soap client configurations.
-#
-# + soapVersion - SOAP version
-public type ClientConfiguration record {|
- *http:ClientConfiguration;
- SoapVersion soapVersion = SOAP11;
-|};
-
-# Object for the basic SOAP client endpoint.
-public isolated client class Client {
- private final http:Client soapClient;
- private final SoapVersion soapVersion;
-
- # Gets invoked during object initialization.
- #
- # + url - URL endpoint
- # + config - Configurations for SOAP client
- # + return - `error` in case of errors or `()` otherwise
- public function init(string url, *ClientConfiguration config) returns Error? {
- self.soapVersion = config.soapVersion;
- do {
- self.soapClient = check new (url, retrieveHttpClientConfig(config));
- } on fail var err {
- return error Error("Failed to initialize soap client", err);
- }
- }
-
- # Sends SOAP request and expects a response.
- # ```ballerina
- # xml|mime:Entity[] response = check soapClient->sendReceive(body);
- # ```
- #
- # + body - SOAP request body as an `XML` or `mime:Entity[]` to work with SOAP attachments
- # + action - SOAP action as a `string`
- # + headers - SOAP headers as a `map`
- # + return - If successful, returns the response. Else, returns an error
- remote function sendReceive(xml|mime:Entity[] body, string action, map headers = {}) returns xml|mime:Entity[]|Error {
- return sendReceive(self.soapVersion, body, self.soapClient, action, headers);
- }
-
- # Fires and forgets requests. Sends the request without the possibility of any response from the
- # service (even an error).
- # ```ballerina
- # check soapClient->sendOnly(body);
- # ```
- #
- # + body - SOAP request body as an `XML` or `mime:Entity[]` to work with SOAP attachments
- # + action - SOAP action as a `string`
- # + headers - SOAP headers as a `map`
- # + return - If successful, returns `nil`. Else, returns an error
- remote function sendOnly(xml|mime:Entity[] body, string action, map headers = {}) returns Error? {
- return sendOnly(self.soapVersion, body, self.soapClient, action, headers);
- }
-}
diff --git a/ballerina/soap_utils.bal b/ballerina/soap_utils.bal
index 7567ff5..ae2bc99 100644
--- a/ballerina/soap_utils.bal
+++ b/ballerina/soap_utils.bal
@@ -1,106 +1,210 @@
-// Copyright (c) 2018, 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/http;
-import ballerina/mime;
-
-# Creates a SOAP Request as an `http:Request`
-#
-# + soapAction - SOAP action
-# + body - SOAP request body as an `XML` or `mime:Entity[]` to work with soap attachments
-# + soapVersion - The SOAP version of the request
-# + headers - SOAP headers as a `map`
-# + return - The SOAP Request sent as `http:Request`
-function createHttpRequest(SoapVersion soapVersion, xml|mime:Entity[] body, string soapAction, map headers = {})
-returns http:Request {
- http:Request req = new;
- if body is xml {
- req.setXmlPayload(body);
- } else {
- req.setBodyParts(body);
- }
- if soapVersion == SOAP11 {
- req.setHeader(mime:CONTENT_TYPE, mime:TEXT_XML);
- req.addHeader("SOAPAction", soapAction);
- } else {
- map stringMap = {};
- stringMap["action"] = "\"" + soapAction + "\"";
- var mediaType = mime:getMediaType(mime:APPLICATION_SOAP_XML);
- if mediaType is mime:MediaType {
- mediaType.parameters = stringMap;
- req.setHeader(mime:CONTENT_TYPE, mediaType.toString());
- }
- }
- foreach string key in headers.keys() {
- req.addHeader(key, headers[key].toBalString());
- }
-
- return req;
-}
-
-# Creates the SOAP response from the HTTP Response.
-#
-# + response - The request to be sent
-# + soapVersion - The SOAP version of the request
-# + return - The SOAP response created from the `http:Response` or the `error` object when reading the payload
-function createSoapResponse(http:Response response, SoapVersion soapVersion) returns xml|error {
- xml payload = check response.getXmlPayload();
- xmlns "http://schemas.xmlsoap.org/soap/envelope/" as soap11;
- xmlns "http://www.w3.org/2003/05/soap-envelope" as soap12;
-
- return soapVersion == SOAP11 ? payload/ : payload/;
-}
-
-string path = "";
-
-function sendReceive(SoapVersion soapVersion, xml|mime:Entity[] body, http:Client httpClient, string soapAction, map headers = {}) returns xml|Error {
- http:Request req = createHttpRequest(soapVersion, body, soapAction, headers);
- http:Response response;
- do {
- response = check httpClient->post(path, req);
- } on fail var err {
- return error Error("Failed to receive soap response", err);
- }
- do {
- return check createSoapResponse(response, soapVersion);
- } on fail var err {
- return error Error("Failed to create soap response", err);
- }
-}
-
-function sendOnly(SoapVersion soapVersion, xml|mime:Entity[] body, http:Client httpClient, string soapAction, map headers = {}) returns Error? {
- http:Request req = createHttpRequest(SOAP11, body, soapAction, headers);
- do {
- http:Response _ = check httpClient->post(path, req);
- } on fail var err {
- return error Error("Failed to create soap response", err);
- }
-}
-
-function retrieveHttpClientConfig(ClientConfiguration config) returns http:ClientConfiguration {
- return {
- httpVersion: config.httpVersion,
- http1Settings: config.http1Settings,
- http2Settings: config.http2Settings,
- timeout: config.timeout,
- poolConfig: config?.poolConfig,
- auth: config?.auth,
- retryConfig: config?.retryConfig,
- responseLimits: config.responseLimits,
- secureSocket: config?.secureSocket,
- circuitBreaker: config?.circuitBreaker
- };
-}
+// Copyright (c) 2023, WSO2 LLC. (http://www.wso2.com) All Rights Reserved.
+//
+// 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 soap.wssec;
+
+import ballerina/crypto;
+import ballerina/http;
+import ballerina/mime;
+import ballerina/lang.regexp;
+import ballerina/jballerina.java;
+import ballerina/test;
+
+public isolated function validateTransportBindingPolicy(ClientConfig config) returns Error? {
+ if config.httpConfig.secureSocket is () {
+ wssec:InboundSecurityConfig|wssec:InboundSecurityConfig[] securityPolicy = config.inboundSecurity;
+ if securityPolicy is wssec:TransportBindingConfig {
+ return error Error(INVALID_PROTOCOL_ERROR);
+ } else if securityPolicy is wssec:InboundSecurityConfig[] {
+ foreach wssec:InboundSecurityConfig policy in securityPolicy {
+ if policy is wssec:TransportBindingConfig {
+ return error Error(INVALID_PROTOCOL_ERROR);
+ }
+ }
+ }
+ }
+}
+
+public isolated function getReadOnlyClientConfig(ClientConfig original) returns readonly & ClientConfig = @java:Method {
+ 'class: "org.wssec.WsSecurity"
+} external;
+
+public isolated function applySecurityPolicies(wssec:InboundSecurityConfig|wssec:InboundSecurityConfig[] security,
+ xml envelope) returns xml|crypto:Error|wssec:Error {
+ if security is wssec:TimestampTokenConfig {
+ return wssec:applyTimestampToken(envelope, security);
+ } else if security is wssec:UsernameTokenConfig {
+ return wssec:applyUsernameToken(envelope, security);
+ } else if security is wssec:SymmetricBindingConfig {
+ return wssec:applySymmetricBinding(envelope, security);
+ } else if security is wssec:AsymmetricBindingConfig {
+ return wssec:applyAsymmetricBinding(envelope, security);
+ } else if security is wssec:InboundSecurityConfig {
+ return envelope;
+ } else {
+ xml securedEnvelope;
+ foreach wssec:InboundSecurityConfig policy in security {
+ securedEnvelope = check applySecurityPolicies(policy, envelope);
+ }
+ return securedEnvelope;
+ }
+}
+
+public isolated function applyOutboundConfig(wssec:OutboundSecurityConfig outboundSecurity, xml envelope)
+ returns xml|Error {
+ xmlns "http://schemas.xmlsoap.org/soap/envelope/" as soap;
+ xml soapEnvelope = envelope;
+ do {
+ wssec:EncryptionAlgorithm? encryptionAlgorithm = outboundSecurity.decryptionAlgorithm;
+ if encryptionAlgorithm is wssec:EncryptionAlgorithm {
+ crypto:PrivateKey|crypto:PublicKey? clientPrivateKey = outboundSecurity.decryptionKey;
+ if clientPrivateKey is crypto:PrivateKey|crypto:PublicKey {
+ byte[] encData = check wssec:getEncryptedData(soapEnvelope);
+ byte[] decryptDataResult = check crypto:decryptRsaEcb(encData, clientPrivateKey);
+ string decryptedBody = "" + check string:fromBytes(decryptDataResult) + "";
+ string decryptedEnv = regexp:replace(re `.*`, soapEnvelope.toString(),
+ decryptedBody);
+ soapEnvelope = check xml:fromString(decryptedEnv);
+ }
+ }
+ wssec:SignatureAlgorithm? signatureAlgorithm = outboundSecurity.signatureAlgorithm;
+ if signatureAlgorithm is wssec:SignatureAlgorithm {
+ crypto:PublicKey? serverPublicKey = outboundSecurity.verificationKey;
+ if serverPublicKey is crypto:PublicKey {
+ byte[] signatureData = check wssec:getSignatureData(soapEnvelope);
+ check wssec:verifyData((soapEnvelope//*).toString().toBytes(),
+ signatureData, serverPublicKey, signatureAlgorithm);
+ }
+ }
+ return soapEnvelope;
+ } on fail var e {
+ return error Error("Outbound security configurations do not match with the SOAP response.", e.cause());
+ }
+}
+
+public isolated function sendReceive(xml|mime:Entity[] body, http:Client httpClient, string? soapAction = (),
+ map headers = {}, string path = "", boolean soap12 = true)
+ returns xml|Error {
+ http:Request req = soap12 ? createSoap12HttpRequest(body, soapAction, headers)
+ : createSoap11HttpRequest(body, soapAction, headers);
+ do {
+ http:Response response = check httpClient->post(path, req);
+ if soap12 {
+ return check createSoap12Response(response);
+ }
+ return check createSoap11Response(response);
+ } on fail var err {
+ return error Error(SOAP_RESPONSE_ERROR, err);
+ }
+}
+
+public isolated function sendOnly(xml|mime:Entity[] body, http:Client httpClient, string? soapAction = (),
+ map headers = {}, string path = "", boolean soap12 = true) returns Error? {
+ http:Request req = soap12 ? createSoap12HttpRequest(body, soapAction, headers)
+ : createSoap11HttpRequest(body, soapAction, headers);
+ http:Response|http:ClientError response = httpClient->post(path, req);
+ if response is http:ClientError {
+ return error Error(response.message());
+ }
+}
+
+isolated function createSoap11HttpRequest(xml|mime:Entity[] body, string soapAction,
+ map headers = {}) returns http:Request {
+ http:Request req = new;
+ if body is xml {
+ req.setXmlPayload(body);
+ req.setHeader(mime:CONTENT_TYPE, mime:TEXT_XML);
+ } else {
+ req.setBodyParts(body);
+ req.setHeader(mime:CONTENT_TYPE, mime:MULTIPART_MIXED);
+ }
+ req.addHeader(SOAP_ACTION, soapAction);
+ foreach string key in headers.keys() {
+ req.addHeader(key, headers[key].toBalString());
+ }
+ return req;
+}
+
+isolated function createSoap12HttpRequest(xml|mime:Entity[] body, string? soapAction,
+ map headers = {}) returns http:Request {
+ http:Request req = new;
+ if body is xml {
+ req.setXmlPayload(body);
+ } else {
+ req.setBodyParts(body);
+ }
+ if soapAction is string {
+ var mediaType = mime:getMediaType(mime:APPLICATION_SOAP_XML);
+ if mediaType is mime:MediaType {
+ mediaType.parameters = {[ACTION]: soapAction};
+ req.setHeader(mime:CONTENT_TYPE, mediaType.toString());
+ }
+ } else {
+ req.setHeader(mime:CONTENT_TYPE, mime:APPLICATION_SOAP_XML);
+ }
+ foreach string key in headers.keys() {
+ req.addHeader(key, headers[key].toBalString());
+ }
+ return req;
+}
+
+isolated function createSoap12Response(http:Response response) returns xml|error {
+ xml payload = check response.getXmlPayload();
+ xmlns "http://www.w3.org/2003/05/soap-envelope" as soap12;
+ return payload;
+}
+
+isolated function createSoap11Response(http:Response response) returns xml|error {
+ xml payload = check response.getXmlPayload();
+ xmlns "http://schemas.xmlsoap.org/soap/envelope/" as soap11;
+ return payload;
+}
+
+public function assertUsernameToken(string envelopeString, string username, string password,
+ wssec:PasswordType passwordType, string body) returns error? {
+ string:RegExp bodyData = check regexp:fromString(body);
+ test:assertTrue(envelopeString.includesMatch(bodyData));
+ string:RegExp usernameTokenTag = re `.*`;
+ string:RegExp usernameTag = re `${username}`;
+ test:assertTrue(envelopeString.includesMatch(usernameTokenTag));
+ test:assertTrue(envelopeString.includesMatch(usernameTag));
+ string:RegExp passwordTag = re `${password}`;
+ test:assertTrue(envelopeString.includesMatch(passwordTag));
+}
+
+public function assertSymmetricBinding(string envelopeString, string body) returns error? {
+ string:RegExp bodyData = check regexp:fromString(body);
+ test:assertTrue(envelopeString.includesMatch(bodyData));
+ assertSignatureWithoutX509(envelopeString);
+}
+
+public function assertSignatureWithoutX509(string securedEnvelope) {
+ string:RegExp signature = re `.*`;
+ string:RegExp signatureInfo = re `.*`;
+ string:RegExp canonicalizationMethod = re ``;
+ string:RegExp signatureMethod = re ``;
+ string:RegExp transformMethod = re ``;
+ string:RegExp digestMethod = re ``;
+ string:RegExp signatureValue = re `.*`;
+
+ test:assertTrue(securedEnvelope.includesMatch(signature));
+ test:assertTrue(securedEnvelope.includesMatch(signatureInfo));
+ test:assertTrue(securedEnvelope.includesMatch(canonicalizationMethod));
+ test:assertTrue(securedEnvelope.includesMatch(signatureMethod));
+ test:assertTrue(securedEnvelope.includesMatch(transformMethod));
+ test:assertTrue(securedEnvelope.includesMatch(digestMethod));
+ test:assertTrue(securedEnvelope.includesMatch(signatureValue));
+}
diff --git a/ballerina/tests/basic_client_test.bal b/ballerina/tests/basic_client_test.bal
deleted file mode 100644
index db15011..0000000
--- a/ballerina/tests/basic_client_test.bal
+++ /dev/null
@@ -1,138 +0,0 @@
-// Copyright (c) 2023, WSO2 LLC. (http://www.wso2.org) All Rights Reserved.
-//
-// 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/test;
-import ballerina/mime;
-
-@test:Config {}
-function testSendReceive11() returns error? {
- Client soapClient = check new ("http://www.dneonline.com/calculator.asmx?WSDL");
-
- xml body = xml `
-
-
- 2
- 3
-
-
- `;
-
- xml|mime:Entity[] response = check soapClient->sendReceive(body, "http://tempuri.org/Add");
-
- xml expected = xml `5`;
- test:assertEquals(response, expected);
-}
-
-@test:Config {}
-function testSendReceive12() returns error? {
- Client soapClient = check new ("http://www.dneonline.com/calculator.asmx?WSDL", soapVersion = SOAP12);
-
- xml body = xml `
-
-
- 2
- 3
-
-
- `;
-
- xml|mime:Entity[] response = check soapClient->sendReceive(body, "http://tempuri.org/Add");
-
- xml expected = xml `5`;
- test:assertEquals(response, expected);
-}
-
-@test:Config {}
-function testSendOnly11() returns error? {
- Client soapClient = check new ("http://www.dneonline.com/calculator.asmx?WSDL");
-
- xml body = xml `
-
-
- 2
- 3
-
-
- `;
-
- _ = check soapClient->sendOnly(body, "http://tempuri.org/Add");
-}
-
-@test:Config {}
-function testSendOnly12() returns error? {
- Client soapClient = check new ("http://www.dneonline.com/calculator.asmx?WSDL", soapVersion = SOAP12);
-
- xml body = xml `
-
-
- 2
- 3
-
-
- `;
-
- _ = check soapClient->sendOnly(body, "http://tempuri.org/Add");
-}
-
-@test:Config {}
-function testSendReceive11WithHeaders() returns error? {
- Client soapClient = check new ("http://www.dneonline.com/calculator.asmx?WSDL");
-
- xml body = xml `
-
-
- 2
- 3
-
-
- `;
-
- xml|mime:Entity[] response = check soapClient->sendReceive(body, "http://tempuri.org/Add", {foo: ["bar1", "bar2"]});
-
- xml expected = xml `5`;
- test:assertEquals(response, expected);
-}
-
-@test:Config {}
-function testSendReceive12WithHeaders() returns error? {
- Client soapClient = check new ("http://www.dneonline.com/calculator.asmx?WSDL", soapVersion = SOAP12);
-
- xml body = xml `
-
-
- 2
- 3
-
-
- `;
-
- xml|mime:Entity[] response = check soapClient->sendReceive(body, "http://tempuri.org/Add", {foo: ["bar1", "bar2"]});
-
- xml expected = xml `5`;
- test:assertEquals(response, expected);
-}
diff --git a/ballerina/types.bal b/ballerina/types.bal
new file mode 100644
index 0000000..cae653b
--- /dev/null
+++ b/ballerina/types.bal
@@ -0,0 +1,39 @@
+// Copyright (c) 2023, WSO2 LLC. (http://www.wso2.com) All Rights Reserved.
+//
+// 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.
+
+# Represents enums for all the supported password types.
+#
+public enum PasswordType {
+ TEXT,
+ DIGEST,
+ DERIVED_KEY_TEXT,
+ DERIVED_KEY_DIGEST
+}
+
+# Represents enums for all the supported signature algorithms.
+#
+public enum SignatureAlgorithm {
+ RSA_SHA1 = "http://www.w3.org/2000/09/xmldsig#rsa-sha1",
+ RSA_SHA256 = "http://www.w3.org/2001/04/xmldsig-more#rsa-sha256",
+ RSA_SHA384 = "http://www.w3.org/2001/04/xmldsig-more#rsa-sha384",
+ RSA_SHA512 = "http://www.w3.org/2001/04/xmldsig-more#rsa-sha512"
+}
+
+# Represents enums for all the supported encryption algorithms.
+#
+public enum EncryptionAlgorithm {
+ RSA_ECB = "http://www.w3.org/2001/04/xmlenc#rsa-1_5"
+}
diff --git a/build-config/checkstyle/build.gradle b/build-config/checkstyle/build.gradle
index 43f218d..0f0758b 100644
--- a/build-config/checkstyle/build.gradle
+++ b/build-config/checkstyle/build.gradle
@@ -14,7 +14,6 @@
* limitations under the License.
*
*/
-
plugins {
id "de.undercouch.download"
}
diff --git a/build-config/resources/Ballerina.toml b/build-config/resources/Ballerina.toml
index 8d7278d..948bf50 100644
--- a/build-config/resources/Ballerina.toml
+++ b/build-config/resources/Ballerina.toml
@@ -3,8 +3,39 @@ org = "ballerina"
name = "soap"
version = "@toml.version@"
authors = ["Ballerina"]
+export=["soap", "soap.soap11", "soap.soap12"]
keywords = ["soap"]
repository = "https://github.com/ballerina-platform/module-ballerina-soap"
icon = "icon.png"
license = ["Apache-2.0"]
distribution = "2201.8.0"
+
+[build-options]
+observabilityIncluded = true
+
+[platform.java17]
+graalvmCompatible = true
+
+[[platform.java17.dependency]]
+groupId = "io.ballerina.stdlib"
+artifactId = "soap-native"
+version = "0.2.0"
+path = "../native/build/libs/soap-native-0.2.0-SNAPSHOT.jar"
+
+[[platform.java17.dependency]]
+groupId = "org.apache.wss4j"
+artifactId = "wss4j-ws-security-dom"
+version = "3.0.1"
+path = "./lib/wss4j-ws-security-dom-3.0.1.jar"
+
+[[platform.java17.dependency]]
+groupId = "org.apache.wss4j"
+artifactId = "wss4j-ws-security-common"
+version = "3.0.1"
+path = "./lib/wss4j-ws-security-common-3.0.1.jar"
+
+[[platform.java17.dependency]]
+groupId = "org.apache.santuario"
+artifactId = "xmlsec"
+version = "3.0.2"
+path = "./lib/xmlsec-3.0.2.jar"
diff --git a/build.gradle b/build.gradle
index f35aa80..5d2e722 100644
--- a/build.gradle
+++ b/build.gradle
@@ -63,10 +63,15 @@ allprojects {
subprojects {
configurations {
+ externalJars
ballerinaStdLibs
+ jbalTools
}
dependencies {
+ jbalTools ("org.ballerinalang:jballerina-tools:${ballerinaLangVersion}") {
+ transitive = false
+ }
/* Standard libraries */
ballerinaStdLibs "io.ballerina.stdlib:http-ballerina:${project.stdlibHttpVersion}"
ballerinaStdLibs "io.ballerina.stdlib:mime-ballerina:${project.stdlibMimeVersion}"
@@ -74,7 +79,6 @@ subprojects {
ballerinaStdLibs "io.ballerina.stdlib:crypto-ballerina:${project.stdlibCryptoVersion}"
ballerinaStdLibs "io.ballerina.stdlib:time-ballerina:${project.stdlibTimeVersion}"
ballerinaStdLibs "io.ballerina.stdlib:uuid-ballerina:${project.stdlibUuidVersion}"
-
ballerinaStdLibs "io.ballerina.stdlib:auth-ballerina:${project.stdlibAuthVersion}"
ballerinaStdLibs "io.ballerina.stdlib:jwt-ballerina:${project.stdlibJwtVersion}"
ballerinaStdLibs "io.ballerina.stdlib:oauth2-ballerina:${project.stdlibOAuth2Version}"
@@ -87,6 +91,16 @@ subprojects {
ballerinaStdLibs "io.ballerina.stdlib:os-ballerina:${project.stdlibOsVersion}"
ballerinaStdLibs "io.ballerina.stdlib:url-ballerina:${project.stdlibUrlVersion}"
ballerinaStdLibs "io.ballerina.stdlib:io-ballerina:${project.stdlibIoVersion}"
+
+ externalJars (group: 'org.apache.wss4j', name: 'wss4j-ws-security-dom', version: "${wsSecurityDomVersion}") {
+ transitive = false
+ }
+ externalJars (group: 'org.apache.wss4j', name: 'wss4j-ws-security-common', version: "${wsSecurityDomVersion}") {
+ transitive = false
+ }
+ externalJars (group: 'org.apache.santuario', name: 'xmlsec', version: "${xmlSecVersion}") {
+ transitive = false
+ }
}
}
@@ -107,5 +121,6 @@ release {
}
task build {
+ dependsOn('soap-native:build')
dependsOn('soap-ballerina:build')
}
diff --git a/docs/spec/spec.md b/docs/spec/spec.md
index 1f0cffe..e7469af 100644
--- a/docs/spec/spec.md
+++ b/docs/spec/spec.md
@@ -85,7 +85,7 @@ public function main () returns error? {
0
`;
- _ = check soapClient->sendOnly(body);
+ check soapClient->sendOnly(body);
}
```
diff --git a/gradle.properties b/gradle.properties
index 64365b1..1f9d13f 100644
--- a/gradle.properties
+++ b/gradle.properties
@@ -2,35 +2,42 @@ org.gradle.caching=true
group=io.ballerina.stdlib
version=0.2.0-SNAPSHOT
-checkstylePluginVersion=10.12.1
+checkstylePluginVersion=10.12.0
spotbugsPluginVersion=5.0.14
shadowJarPluginVersion=8.1.1
downloadPluginVersion=5.4.0
releasePluginVersion=2.8.0
ballerinaGradlePluginVersion=2.0.1
-ballerinaLangVersion=2201.8.0-20230908-135700-74a59dff
+ballerinaLangVersion=2201.8.0
-stdlibIoVersion=1.6.0-20230911-134800-de06e28
-stdlibTimeVersion=2.4.0-20230911-140200-7f96f1e
-stdlibUrlVersion=2.4.0-20230911-140700-36451a2
+stdlibIoVersion=1.6.0
+stdlibTimeVersion=2.4.0
+stdlibUrlVersion=2.4.0
-stdlibConstraintVersion=1.4.0-20230911-142700-8202202
-stdlibOsVersion=1.8.0-20230911-142700-b182bf4
-stdlibTaskVersion=2.5.0-20230911-143300-237313e
-stdlibLogVersion=2.9.0-20230911-150900-2f32c1a
-stdlibCryptoVersion=2.5.0-20230911-142900-8807d07
+stdlibConstraintVersion=1.4.0
+stdlibCryptoVersion=2.5.0
+stdlibLogVersion=2.9.0
+stdlibOsVersion=1.8.0
+stdlibRandomVersion=1.5.0
+stdlibTaskVersion=2.5.0
-stdlibFileVersion=1.9.0-20230911-153400-738b25e
-stdlibMimeVersion=2.9.0-20230911-153200-3add04c
-stdlibCacheVersion=3.7.0-20230911-145700-142f375
-stdlibUuidVersion=1.7.0-20230911-150900-6c4771f
+stdlibCacheVersion=3.7.0
+stdlibFileVersion=1.9.0
+stdlibMimeVersion=2.9.0
+stdlibUuidVersion=1.7.0
-stdlibAuthVersion=2.10.0-20230911-153500-8c3c5cb
-stdlibJwtVersion=2.10.0-20230911-153400-b5de47b
-stdlibOAuth2Version=2.10.0-20230911-153600-6710ec0
+stdlibAuthVersion=2.10.0
+stdlibJwtVersion=2.10.0
+stdlibOAuth2Version=2.10.0
-stdlibHttpVersion=2.10.0-20230911-204400-1c092fe
+stdlibHttpVersion=2.10.1
-observeVersion=1.2.0-20230911-133500-b3d8db3
-observeInternalVersion=1.2.0-20230911-141700-4c0454a
+stdlibTransactionVersion=1.8.0
+wsSecurityDomVersion=3.0.1
+wsSecurityCommonVersion=3.0.1
+xmlSecVersion=3.0.2
+
+# Ballerinax Observer
+observeVersion=1.2.0
+observeInternalVersion=1.2.0
diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties
index 9f4197d..ac72c34 100644
--- a/gradle/wrapper/gradle-wrapper.properties
+++ b/gradle/wrapper/gradle-wrapper.properties
@@ -1,6 +1,6 @@
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
-distributionUrl=https\://services.gradle.org/distributions/gradle-8.2.1-bin.zip
+distributionUrl=https\://services.gradle.org/distributions/gradle-8.3-bin.zip
networkTimeout=10000
validateDistributionUrl=true
zipStoreBase=GRADLE_USER_HOME
diff --git a/gradlew b/gradlew
index fcb6fca..0adc8e1 100755
--- a/gradlew
+++ b/gradlew
@@ -83,7 +83,8 @@ done
# This is normally unused
# shellcheck disable=SC2034
APP_BASE_NAME=${0##*/}
-APP_HOME=$( cd "${APP_HOME:-./}" && pwd -P ) || exit
+# Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036)
+APP_HOME=$( cd "${APP_HOME:-./}" > /dev/null && pwd -P ) || exit
# Use the maximum available, or set MAX_FD != -1 to use that value.
MAX_FD=maximum
diff --git a/native/build.gradle b/native/build.gradle
new file mode 100644
index 0000000..482d386
--- /dev/null
+++ b/native/build.gradle
@@ -0,0 +1,94 @@
+/*
+ * Copyright (c) 2023, WSO2 LLC. (http://www.wso2.com) All Rights Reserved.
+ *
+ * Licensed 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.
+ *
+ */
+
+plugins {
+ id 'java'
+ id 'checkstyle'
+ id 'com.github.spotbugs'
+}
+
+description = 'Ballerina - SOAP Java Utils'
+
+dependencies {
+ checkstyle project(':checkstyle')
+ checkstyle "com.puppycrawl.tools:checkstyle:${checkstylePluginVersion}"
+
+ implementation group: 'org.ballerinalang', name: 'ballerina-runtime', version: "${ballerinaLangVersion}"
+ implementation 'com.google.guava:guava:32.1.1-jre'
+ implementation 'org.apache.wss4j:wss4j-ws-security-dom:3.0.1'
+ implementation 'org.apache.santuario:xmlsec:3.0.2'
+ implementation 'org.apache.wss4j:wss4j-ws-security-common:3.0.1'
+ compileOnly group: 'org.graalvm.nativeimage', name: 'svm', version: '22.2.0'
+}
+
+checkstyle {
+ toolVersion "${project.checkstylePluginVersion}"
+ configFile rootProject.file("build-config/checkstyle/build/checkstyle.xml")
+ configProperties = ["suppressionFile" : file("${rootDir}/build-config/checkstyle/build/suppressions.xml")]
+}
+
+checkstyleMain.dependsOn(":checkstyle:downloadCheckstyleRuleFiles")
+
+spotbugsMain {
+ effort "max"
+ reportLevel "low"
+ reportsDir = file("$project.buildDir/reports/spotbugs")
+ reports {
+ html.enabled true
+ text.enabled = true
+ }
+ def excludeFile = file("${rootDir}/spotbugs-exclude.xml")
+ if(excludeFile.exists()) {
+ excludeFilter = excludeFile
+ }
+}
+
+def excludePattern = '**/module-info.java'
+tasks.withType(Checkstyle) {
+ exclude excludePattern
+}
+
+publishing {
+ publications {
+ mavenJava(MavenPublication) {
+ groupId project.group
+ artifactId "soap-native"
+ version = project.version
+ artifact jar
+ }
+ }
+
+ repositories {
+ maven {
+ name = "GitHubPackages"
+ url = uri("https://maven.pkg.github.com/ballerina-platform/module-ballerina-constraint")
+ credentials {
+ username = System.getenv("publishUser")
+ password = System.getenv("publishPAT")
+ }
+ }
+ }
+}
+
+compileJava {
+ doFirst {
+ options.compilerArgs = [
+ '--module-path', classpath.asPath,
+ ]
+ classpath = files()
+ }
+}
diff --git a/native/src/main/java/module-info.java b/native/src/main/java/module-info.java
new file mode 100644
index 0000000..4168a3f
--- /dev/null
+++ b/native/src/main/java/module-info.java
@@ -0,0 +1,25 @@
+// Copyright (c) 2023, WSO2 LLC. (http://www.wso2.com) All Rights Reserved.
+//
+// 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.
+
+module soap {
+ requires io.ballerina.runtime;
+ requires org.apache.wss4j.dom;
+ requires org.apache.wss4j.common;
+ requires org.apache.santuario.xmlsec;
+ requires java.xml;
+ requires java.xml.crypto;
+ requires org.apache.commons.codec;
+}
diff --git a/native/src/main/java/org/wssec/Constants.java b/native/src/main/java/org/wssec/Constants.java
new file mode 100644
index 0000000..84b60f2
--- /dev/null
+++ b/native/src/main/java/org/wssec/Constants.java
@@ -0,0 +1,46 @@
+// Copyright (c) 2023, WSO2 LLC. (http://www.wso2.com) All Rights Reserved.
+//
+// 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 org.wssec;
+
+public class Constants {
+
+ private Constants() {
+ }
+
+ public static final int ITERATION = 1000;
+ public static final String DIGEST = "DIGEST";
+ public static final String DERIVED_KEY_TEXT = "DERIVED_KEY_TEXT";
+ public static final String DERIVED_KEY_DIGEST = "DERIVED_KEY_DIGEST";
+ public static final String PASSWORD = "password";
+ public static final String NATIVE_SEC_HEADER = "nativeSecHeader";
+ public static final String NATIVE_DOCUMENT = "nativeDocumentBuilder";
+ public static final String NATIVE_SIGNATURE = "nativeSignature";
+ public static final String NATIVE_ENCRYPTION = "nativeEncryption";
+ public static final String SIGNATURE_VALUE_TAG = "ds:SignatureValue";
+ public static final String SIGNATURE_METHOD_TAG = "ds:SignatureMethod";
+ public static final String ALGORITHM = "Algorithm";
+ public static final String KEY_INFO_TAG = "ds:KeyInfo";
+ public static final String CIPHER_DATA_TAG = "xenc:CipherData";
+ public static final String XML_ENC_NS = "xmlns:xenc";
+ public static final String XML_DS_NS = "xmlns:ds";
+ public static final String ENCRYPTED_KEY_TAG = "xenc:EncryptedKey";
+ public static final String ENCRYPTION_METHOD_TAG = "xenc:EncryptionMethod";
+ public static final String NAMESPACE_URI_ENC = "http://www.w3.org/2001/04/xmlenc#";
+ public static final String CIPHER_VALUE_TAG = "CipherValue";
+ public static final String X509 = "X.509";
+ public static final String EMPTY_XML_DOCUMENT_ERROR = "XML Document is empty";
+}
diff --git a/native/src/main/java/org/wssec/DocumentBuilder.java b/native/src/main/java/org/wssec/DocumentBuilder.java
new file mode 100644
index 0000000..5e90b47
--- /dev/null
+++ b/native/src/main/java/org/wssec/DocumentBuilder.java
@@ -0,0 +1,60 @@
+// Copyright (c) 2023, WSO2 LLC. (http://www.wso2.com) All Rights Reserved.
+//
+// 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 org.wssec;
+
+import io.ballerina.runtime.api.creators.ValueCreator;
+import io.ballerina.runtime.api.values.BArray;
+import io.ballerina.runtime.api.values.BObject;
+import io.ballerina.runtime.api.values.BXml;
+import org.w3c.dom.Document;
+import org.xml.sax.InputSource;
+
+import java.io.StringReader;
+
+import javax.xml.parsers.DocumentBuilderFactory;
+
+import static org.wssec.Constants.NATIVE_DOCUMENT;
+
+public class DocumentBuilder {
+
+ private final Document document;
+
+ public DocumentBuilder(BObject documentBuilder, BXml xmlPayload) throws Exception {
+ DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
+ factory.setNamespaceAware(true);
+ this.document = factory.newDocumentBuilder().parse(new InputSource(new StringReader(xmlPayload.toString())));
+ documentBuilder.addNativeData(NATIVE_DOCUMENT, this.document);
+ }
+
+ protected DocumentBuilder(Document document) {
+ this.document = document;
+ }
+
+ public static BArray getSignatureData(BObject document) {
+ Document nativeDocument = (Document) document.getNativeData().get(NATIVE_DOCUMENT);
+ return ValueCreator.createArrayValue(WsSecurityUtils.getSignatureValue(nativeDocument));
+ }
+
+ public static BArray getEncryptedData(BObject document) {
+ Document nativeDocument = (Document) document.getNativeData().get(NATIVE_DOCUMENT);
+ return ValueCreator.createArrayValue(WsSecurityUtils.getEncryptedData(nativeDocument));
+ }
+
+ protected Document getNativeDocument() {
+ return this.document;
+ }
+}
diff --git a/native/src/main/java/org/wssec/Encryption.java b/native/src/main/java/org/wssec/Encryption.java
new file mode 100644
index 0000000..c7ced8d
--- /dev/null
+++ b/native/src/main/java/org/wssec/Encryption.java
@@ -0,0 +1,75 @@
+// Copyright (c) 2023, WSO2 LLC. (http://www.wso2.com) All Rights Reserved.
+//
+// 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 org.wssec;
+
+import io.ballerina.runtime.api.creators.ValueCreator;
+import io.ballerina.runtime.api.utils.StringUtils;
+import io.ballerina.runtime.api.values.BArray;
+import io.ballerina.runtime.api.values.BHandle;
+import io.ballerina.runtime.api.values.BObject;
+import io.ballerina.runtime.api.values.BString;
+
+import static org.wssec.Constants.NATIVE_ENCRYPTION;
+import static org.wssec.Utils.createError;
+
+public class Encryption {
+
+ private String encryptionAlgorithm;
+ private byte[] encryptedData;
+
+ public static void setEncryptionAlgorithm(BObject encrypt, BString encryptionAlgorithm) {
+ BHandle handle = (BHandle) encrypt.get(StringUtils.fromString(NATIVE_ENCRYPTION));
+ Encryption encryption = (Encryption) handle.getValue();
+ encryption.setEncryptionAlgorithm(encryptionAlgorithm.getValue());
+ }
+
+ public static void setEncryptedData(BObject encrypt, BArray encryptedData) {
+ BHandle handle = (BHandle) encrypt.get(StringUtils.fromString(NATIVE_ENCRYPTION));
+ Encryption encryption = (Encryption) handle.getValue();
+ encryption.setEncryptedData(encryptedData.getByteArray());
+ }
+
+ public static BArray getEncryptedData(BObject encrypt) {
+ BHandle handle = (BHandle) encrypt.get(StringUtils.fromString(NATIVE_ENCRYPTION));
+ Encryption encryption = (Encryption) handle.getValue();
+ return ValueCreator.createArrayValue(encryption.getEncryptedData());
+ }
+
+ public static Object getEncryptedKeyElements(BArray encryptedData) {
+ try {
+ return WsSecurityUtils.getEncryptedKeyElement(encryptedData.getByteArray());
+ } catch (Exception e) {
+ return createError(e.getMessage());
+ }
+ }
+
+ protected String getEncryptionAlgorithm() {
+ return encryptionAlgorithm;
+ }
+
+ protected byte[] getEncryptedData() {
+ return encryptedData;
+ }
+
+ protected void setEncryptionAlgorithm(String encryptionAlgorithm) {
+ this.encryptionAlgorithm = encryptionAlgorithm;
+ }
+
+ protected void setEncryptedData(byte[] encryptedData) {
+ this.encryptedData = encryptedData;
+ }
+}
diff --git a/native/src/main/java/org/wssec/ModuleUtils.java b/native/src/main/java/org/wssec/ModuleUtils.java
new file mode 100644
index 0000000..61b7d62
--- /dev/null
+++ b/native/src/main/java/org/wssec/ModuleUtils.java
@@ -0,0 +1,35 @@
+// Copyright (c) 2023, WSO2 LLC. (http://www.wso2.com) All Rights Reserved.
+//
+// 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 org.wssec;
+
+import io.ballerina.runtime.api.Environment;
+import io.ballerina.runtime.api.Module;
+
+public class ModuleUtils {
+
+ private static Module module;
+
+ private ModuleUtils() {}
+
+ public static void setModule(Environment environment) {
+ module = environment.getCurrentModule();
+ }
+
+ public static Module getModule() {
+ return module;
+ }
+}
diff --git a/native/src/main/java/org/wssec/Signature.java b/native/src/main/java/org/wssec/Signature.java
new file mode 100644
index 0000000..e9d72b6
--- /dev/null
+++ b/native/src/main/java/org/wssec/Signature.java
@@ -0,0 +1,66 @@
+// Copyright (c) 2023, WSO2 LLC. (http://www.wso2.com) All Rights Reserved.
+//
+// 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 org.wssec;
+
+import io.ballerina.runtime.api.creators.ValueCreator;
+import io.ballerina.runtime.api.utils.StringUtils;
+import io.ballerina.runtime.api.values.BArray;
+import io.ballerina.runtime.api.values.BHandle;
+import io.ballerina.runtime.api.values.BObject;
+import io.ballerina.runtime.api.values.BString;
+
+import static org.wssec.Constants.NATIVE_SIGNATURE;
+
+public class Signature {
+
+ private String signatureAlgorithm;
+ private byte[] signatureValue;
+
+ public static void setSignatureAlgorithm(BObject sign, BString signatureAlgorithm) {
+ BHandle handle = (BHandle) sign.get(StringUtils.fromString(NATIVE_SIGNATURE));
+ Signature signature = (Signature) handle.getValue();
+ signature.setSignatureAlgorithm(signatureAlgorithm.getValue());
+ }
+
+ public static void setSignatureValue(BObject sign, BArray signatureValue) {
+ BHandle handle = (BHandle) sign.get(StringUtils.fromString(NATIVE_SIGNATURE));
+ Signature signature = (Signature) handle.getValue();
+ signature.setSignatureValue(signatureValue.getByteArray());
+ }
+
+ public static BArray getSignatureValue(BObject sign) {
+ BHandle handle = (BHandle) sign.get(StringUtils.fromString(NATIVE_SIGNATURE));
+ Signature signature = (Signature) handle.getValue();
+ return ValueCreator.createArrayValue(signature.getSignatureValue());
+ }
+
+ protected String getSignatureAlgorithm() {
+ return signatureAlgorithm;
+ }
+
+ protected void setSignatureAlgorithm(String signatureAlgorithm) {
+ this.signatureAlgorithm = signatureAlgorithm;
+ }
+
+ protected byte[] getSignatureValue() {
+ return signatureValue;
+ }
+
+ protected void setSignatureValue(byte[] signatureValue) {
+ this.signatureValue = signatureValue;
+ }
+}
diff --git a/native/src/main/java/org/wssec/Utils.java b/native/src/main/java/org/wssec/Utils.java
new file mode 100644
index 0000000..29691c0
--- /dev/null
+++ b/native/src/main/java/org/wssec/Utils.java
@@ -0,0 +1,41 @@
+// Copyright (c) 2023, WSO2 LLC. (http://www.wso2.com) All Rights Reserved.
+//
+// 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 org.wssec;
+
+import io.ballerina.runtime.api.creators.ErrorCreator;
+import io.ballerina.runtime.api.utils.StringUtils;
+import io.ballerina.runtime.api.values.BError;
+
+import static org.wssec.ModuleUtils.getModule;
+
+public class Utils {
+
+ private Utils() {
+ }
+
+ public static final String ERROR_TYPE = "Error";
+ public static final String POLICY_NOT_SUPPORTED_ERROR = "Given ws security policy is currently not supported";
+
+ public static BError createError(String message) {
+ return ErrorCreator.createError(getModule(), ERROR_TYPE,
+ StringUtils.fromString(message), null, null);
+ }
+
+ public static BError createError(String message, BError cause) {
+ return ErrorCreator.createError(getModule(), ERROR_TYPE, StringUtils.fromString(message), cause, null);
+ }
+}
diff --git a/native/src/main/java/org/wssec/WsSecurity.java b/native/src/main/java/org/wssec/WsSecurity.java
new file mode 100644
index 0000000..5e74312
--- /dev/null
+++ b/native/src/main/java/org/wssec/WsSecurity.java
@@ -0,0 +1,174 @@
+// Copyright (c) 2023, WSO2 LLC. (http://www.wso2.com) All Rights Reserved.
+//
+// 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 org.wssec;
+
+import io.ballerina.runtime.api.utils.StringUtils;
+import io.ballerina.runtime.api.values.BHandle;
+import io.ballerina.runtime.api.values.BMap;
+import io.ballerina.runtime.api.values.BObject;
+import io.ballerina.runtime.api.values.BString;
+import org.apache.wss4j.common.ext.WSSecurityException;
+import org.apache.wss4j.common.util.UsernameTokenUtil;
+import org.apache.wss4j.dom.WSDocInfo;
+import org.apache.wss4j.dom.engine.WSSConfig;
+import org.apache.wss4j.dom.handler.RequestData;
+import org.apache.wss4j.dom.message.WSSecDKEncrypt;
+import org.apache.wss4j.dom.message.WSSecSignature;
+import org.apache.wss4j.dom.message.WSSecTimestamp;
+import org.apache.wss4j.dom.message.WSSecUsernameToken;
+import org.apache.xml.security.Init;
+import org.apache.xml.security.algorithms.JCEMapper;
+import org.w3c.dom.Document;
+
+import java.io.FileInputStream;
+import java.io.IOException;
+import java.security.cert.CertificateException;
+import java.security.cert.CertificateFactory;
+import java.security.cert.X509Certificate;
+
+import static org.apache.wss4j.common.WSS4JConstants.AES_128_GCM;
+import static org.apache.wss4j.common.WSS4JConstants.HMAC_SHA1;
+import static org.apache.wss4j.dom.WSConstants.CUSTOM_KEY_IDENTIFIER;
+import static org.apache.wss4j.dom.WSConstants.X509_KEY_IDENTIFIER;
+import static org.wssec.Constants.DERIVED_KEY_DIGEST;
+import static org.wssec.Constants.DERIVED_KEY_TEXT;
+import static org.wssec.Constants.ITERATION;
+import static org.wssec.Constants.NATIVE_ENCRYPTION;
+import static org.wssec.Constants.NATIVE_SEC_HEADER;
+import static org.wssec.Constants.NATIVE_SIGNATURE;
+import static org.wssec.Constants.PASSWORD;
+import static org.wssec.Constants.X509;
+import static org.wssec.Utils.createError;
+import static org.wssec.WsSecurityUtils.convertDocumentToString;
+import static org.wssec.WsSecurityUtils.setUTChildElements;
+
+public class WsSecurity {
+
+ public static Object applyUsernameTokenPolicy(BObject wsSecHeader, BString username,
+ BString password, BString passwordType) {
+ BHandle handle = (BHandle) wsSecHeader.get(StringUtils.fromString(NATIVE_SEC_HEADER));
+ WsSecurityHeader wsSecurityHeader = (WsSecurityHeader) handle.getValue();
+ WSSecUsernameToken usernameToken = new WSSecUsernameToken(wsSecurityHeader.getWsSecHeader());
+ setUTChildElements(usernameToken, passwordType.getValue(), username.getValue(), password.getValue());
+ Document xmlDocument;
+ switch (passwordType.getValue()) {
+ case DERIVED_KEY_TEXT, DERIVED_KEY_DIGEST -> {
+ usernameToken.addDerivedKey(Constants.ITERATION);
+ xmlDocument = usernameToken.build(UsernameTokenUtil.generateSalt(true));
+ }
+ default -> xmlDocument = usernameToken.build();
+ }
+ try {
+ return convertDocumentToString(xmlDocument);
+ } catch (Exception e) {
+ return createError(e.getMessage());
+ }
+ }
+
+ public static Object applyTimestampPolicy(BObject wsSecHeader, int timeToLive) {
+ BHandle handle = (BHandle) wsSecHeader.get(StringUtils.fromString(NATIVE_SEC_HEADER));
+ WsSecurityHeader wsSecurityHeader = (WsSecurityHeader) handle.getValue();
+ WSSecTimestamp timestamp = new WSSecTimestamp(wsSecurityHeader.getWsSecHeader());
+ timestamp.setTimeToLive(timeToLive);
+ try {
+ return convertDocumentToString(timestamp.build());
+ } catch (Exception e) {
+ return createError(e.getMessage());
+ }
+ }
+
+ public static Object applySignatureOnlyPolicy(BObject wsSecHeader, BObject balSignature, Object x509FilePath) {
+ BHandle handle = (BHandle) wsSecHeader.get(StringUtils.fromString(NATIVE_SEC_HEADER));
+ WsSecurityHeader wsSecurityHeader = (WsSecurityHeader) handle.getValue();
+ handle = (BHandle) balSignature.get(StringUtils.fromString(NATIVE_SIGNATURE));
+ Signature signature = (Signature) handle.getValue();
+ try {
+ Document xmlDocument = createSignatureTags(wsSecurityHeader, x509FilePath);
+ WsSecurityUtils.setSignatureValue(xmlDocument, signature.getSignatureValue(),
+ signature.getSignatureAlgorithm());
+ return convertDocumentToString(xmlDocument);
+ } catch (Exception e) {
+ return createError(e.getMessage());
+ }
+ }
+
+ public static Object applyEncryptionOnlyPolicy(BObject wsSecHeader, BObject balEncryption) {
+ BHandle handle = (BHandle) wsSecHeader.get(StringUtils.fromString(NATIVE_SEC_HEADER));
+ WsSecurityHeader wsSecurityHeader = (WsSecurityHeader) handle.getValue();
+ handle = (BHandle) balEncryption.get(StringUtils.fromString(NATIVE_ENCRYPTION));
+ Encryption encryption = (Encryption) handle.getValue();
+ try {
+ byte[] key = UsernameTokenUtil.generateDerivedKey(PASSWORD,
+ UsernameTokenUtil.generateSalt(true), ITERATION);
+ Document xmlDocument = encryptEnvelope(wsSecurityHeader, key);
+ WsSecurityUtils.setEncryptedData(xmlDocument, encryption.getEncryptedData(),
+ encryption.getEncryptionAlgorithm());
+ return convertDocumentToString(xmlDocument);
+ } catch (Exception e) {
+ return createError(e.getMessage());
+ }
+ }
+
+ public static Document encryptEnvelope(WsSecurityHeader wsSecurityHeader, byte[] rawKey)
+ throws WSSecurityException {
+ Init.init();
+ JCEMapper.registerDefaultAlgorithms();
+ WSSecDKEncrypt encryptionBuilder = new WSSecDKEncrypt(wsSecurityHeader.getWsSecHeader());
+ encryptionBuilder.setSymmetricEncAlgorithm(AES_128_GCM);
+ return encryptionBuilder.build(rawKey);
+ }
+
+ public static Document createSignatureTags(WsSecurityHeader wsSecurityHeader,
+ Object x509FilePath) throws Exception {
+ RequestData reqData = new RequestData();
+ reqData.setSecHeader(wsSecurityHeader.getWsSecHeader());
+ reqData.setWssConfig(WSSConfig.getNewInstance());
+ reqData.setWsDocInfo(new WSDocInfo(wsSecurityHeader.getDocument()));
+ WSSecSignature wsSecSignature = prepareSignature(reqData, x509FilePath);
+ WsSecurityUtils.buildSignature(reqData, wsSecSignature);
+ return wsSecSignature.build(null);
+ }
+
+ public static WSSecSignature prepareSignature(RequestData reqData, Object x509FilePath) {
+ WSSecSignature sign = new WSSecSignature(reqData.getSecHeader());
+ try {
+ byte[] key = UsernameTokenUtil.generateDerivedKey(PASSWORD,
+ UsernameTokenUtil.generateSalt(true), ITERATION);
+ sign.setSecretKey(key);
+ sign.setWsDocInfo(reqData.getWsDocInfo());
+ sign.setSignatureAlgorithm(HMAC_SHA1);
+ sign.setKeyIdentifierType(CUSTOM_KEY_IDENTIFIER);
+ if (x509FilePath != null) {
+ FileInputStream fis = new FileInputStream(x509FilePath.toString());
+ CertificateFactory certificateFactory = CertificateFactory.getInstance(X509);
+ X509Certificate x509Certificate = (X509Certificate) certificateFactory.generateCertificate(fis);
+ sign.setKeyIdentifierType(X509_KEY_IDENTIFIER);
+ sign.setX509Certificate(x509Certificate);
+ fis.close();
+ }
+ sign.prepare(null);
+ } catch (CertificateException | WSSecurityException | IOException e) {
+ throw createError(e.getMessage());
+ }
+ return sign;
+ }
+
+ public static BMap getReadOnlyClientConfig(BMap securityConfig) {
+ securityConfig.freezeDirect();
+ return securityConfig;
+ }
+}
diff --git a/native/src/main/java/org/wssec/WsSecurityHeader.java b/native/src/main/java/org/wssec/WsSecurityHeader.java
new file mode 100644
index 0000000..be1e09f
--- /dev/null
+++ b/native/src/main/java/org/wssec/WsSecurityHeader.java
@@ -0,0 +1,60 @@
+// Copyright (c) 2023, WSO2 LLC. (http://www.wso2.com) All Rights Reserved.
+//
+// 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 org.wssec;
+
+import io.ballerina.runtime.api.utils.StringUtils;
+import io.ballerina.runtime.api.values.BError;
+import io.ballerina.runtime.api.values.BHandle;
+import io.ballerina.runtime.api.values.BObject;
+import org.apache.wss4j.common.ext.WSSecurityException;
+import org.apache.wss4j.dom.message.WSSecHeader;
+import org.w3c.dom.Document;
+
+import static org.wssec.Constants.NATIVE_DOCUMENT;
+import static org.wssec.Constants.NATIVE_SEC_HEADER;
+import static org.wssec.Utils.createError;
+
+public class WsSecurityHeader {
+
+ private final WSSecHeader wsSecHeader;
+ private final Document document;
+
+ public WsSecurityHeader(BObject documentBuilder) {
+ Document document = (Document) documentBuilder.getNativeData().get(NATIVE_DOCUMENT);
+ this.wsSecHeader = new WSSecHeader(document);
+ this.document = document;
+ }
+
+ protected Document getDocument() {
+ return document;
+ }
+
+ protected WSSecHeader getWsSecHeader() {
+ return wsSecHeader;
+ }
+
+ public static BError insertSecHeader(BObject secHeader) {
+ BHandle handle = (BHandle) secHeader.get(StringUtils.fromString(NATIVE_SEC_HEADER));
+ WsSecurityHeader wsSecurityHeader = (WsSecurityHeader) handle.getValue();
+ try {
+ wsSecurityHeader.getWsSecHeader().insertSecurityHeader();
+ } catch (WSSecurityException e) {
+ return createError(e.getMessage());
+ }
+ return null;
+ }
+}
diff --git a/native/src/main/java/org/wssec/WsSecurityUtils.java b/native/src/main/java/org/wssec/WsSecurityUtils.java
new file mode 100644
index 0000000..38c45e4
--- /dev/null
+++ b/native/src/main/java/org/wssec/WsSecurityUtils.java
@@ -0,0 +1,139 @@
+// Copyright (c) 2023, WSO2 LLC. (http://www.wso2.com) All Rights Reserved.
+//
+// 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 org.wssec;
+
+import io.ballerina.runtime.api.creators.ErrorCreator;
+import io.ballerina.runtime.api.utils.StringUtils;
+import org.apache.wss4j.common.WSEncryptionPart;
+import org.apache.wss4j.dom.handler.RequestData;
+import org.apache.wss4j.dom.message.WSSecSignature;
+import org.apache.wss4j.dom.message.WSSecUsernameToken;
+import org.apache.wss4j.dom.util.WSSecurityUtil;
+import org.w3c.dom.Document;
+import org.w3c.dom.Element;
+import org.w3c.dom.NodeList;
+
+import java.io.StringWriter;
+import java.util.ArrayList;
+import java.util.Base64;
+import java.util.List;
+
+import javax.xml.crypto.dsig.Reference;
+import javax.xml.parsers.DocumentBuilderFactory;
+import javax.xml.transform.Transformer;
+import javax.xml.transform.TransformerFactory;
+import javax.xml.transform.dom.DOMSource;
+import javax.xml.transform.stream.StreamResult;
+
+import static org.apache.wss4j.common.WSS4JConstants.ENC_NS;
+import static org.apache.wss4j.common.WSS4JConstants.KEYTRANSPORT_RSA15;
+import static org.apache.wss4j.common.WSS4JConstants.PASSWORD_DIGEST;
+import static org.apache.wss4j.common.WSS4JConstants.PASSWORD_TEXT;
+import static org.apache.wss4j.common.WSS4JConstants.SIG_NS;
+import static org.wssec.Constants.ALGORITHM;
+import static org.wssec.Constants.CIPHER_DATA_TAG;
+import static org.wssec.Constants.CIPHER_VALUE_TAG;
+import static org.wssec.Constants.DERIVED_KEY_DIGEST;
+import static org.wssec.Constants.DIGEST;
+import static org.wssec.Constants.EMPTY_XML_DOCUMENT_ERROR;
+import static org.wssec.Constants.ENCRYPTED_KEY_TAG;
+import static org.wssec.Constants.ENCRYPTION_METHOD_TAG;
+import static org.wssec.Constants.KEY_INFO_TAG;
+import static org.wssec.Constants.NAMESPACE_URI_ENC;
+import static org.wssec.Constants.SIGNATURE_METHOD_TAG;
+import static org.wssec.Constants.SIGNATURE_VALUE_TAG;
+import static org.wssec.Constants.XML_DS_NS;
+import static org.wssec.Constants.XML_ENC_NS;
+
+public class WsSecurityUtils {
+
+ public static void buildSignature(RequestData reqData, WSSecSignature sign) throws Exception {
+ List parts = new ArrayList<>(1);
+ Document doc = reqData.getSecHeader().getSecurityHeaderElement().getOwnerDocument();
+ parts.add(WSSecurityUtil.getDefaultEncryptionPart(doc));
+ List referenceList = sign.addReferencesToSign(parts);
+ sign.computeSignature(referenceList);
+ reqData.getSignatureValues().add(sign.getSignatureValue());
+ }
+
+ public static void setSignatureValue(Document doc, byte[] signature, String algorithm) {
+ doc.getElementsByTagName(SIGNATURE_METHOD_TAG)
+ .item(0).getAttributes().item(0).setNodeValue(algorithm);
+ NodeList digestValueList = doc.getElementsByTagName(SIGNATURE_VALUE_TAG);
+ digestValueList.item(0).getFirstChild().setNodeValue(Base64.getEncoder().encodeToString(signature));
+ }
+
+ public static byte[] getSignatureValue(Document doc) {
+ String signature = doc.getElementsByTagName(SIGNATURE_VALUE_TAG).item(0).getFirstChild().getNodeValue();
+ return Base64.getDecoder().decode(signature);
+ }
+
+ public static void setEncryptedData(Document doc, byte[] encryptedData, String algorithm) {
+ Element cipherDataElement = (Element) doc
+ .getElementsByTagNameNS(NAMESPACE_URI_ENC, CIPHER_VALUE_TAG).item(0);
+ cipherDataElement.getFirstChild().setNodeValue(Base64.getEncoder().encodeToString(encryptedData));
+ doc.getElementsByTagName(ENCRYPTION_METHOD_TAG).item(0).getAttributes().item(0)
+ .setNodeValue(algorithm);
+ }
+
+ public static byte[] getEncryptedData(Document document) {
+ String encryptedText = document
+ .getElementsByTagNameNS(NAMESPACE_URI_ENC, CIPHER_VALUE_TAG).item(0)
+ .getFirstChild().getNodeValue();
+ return Base64.getDecoder().decode(encryptedText);
+ }
+
+ public static Object getEncryptedKeyElement(byte[] encryptKey) throws Exception {
+ Document document = DocumentBuilderFactory.newInstance().newDocumentBuilder().newDocument();
+ Element encryptedKey = document.createElement(ENCRYPTED_KEY_TAG);
+ encryptedKey.setAttribute(XML_ENC_NS, ENC_NS);
+ Element encryptionMethod = document.createElement(ENCRYPTION_METHOD_TAG);
+ encryptionMethod.setAttribute(ALGORITHM, KEYTRANSPORT_RSA15);
+ encryptedKey.appendChild(encryptionMethod);
+ Element keyInfo = document.createElement(KEY_INFO_TAG);
+ keyInfo.setAttribute(XML_DS_NS, SIG_NS);
+ encryptedKey.appendChild(keyInfo);
+ Element cipherData = document.createElement(CIPHER_DATA_TAG);
+ cipherData.appendChild(document.createTextNode(Base64.getEncoder().encodeToString(encryptKey)));
+ encryptedKey.appendChild(cipherData);
+ document.appendChild(encryptedKey);
+ return convertDocumentToString(document);
+ }
+
+ public static void setUTChildElements(WSSecUsernameToken usernameToken, String passwordType,
+ String username, String password) {
+ if (DIGEST.equals(passwordType) || DERIVED_KEY_DIGEST.equals(passwordType)) {
+ usernameToken.setPasswordType(PASSWORD_DIGEST);
+ usernameToken.setUserInfo(username, password);
+ usernameToken.addCreated();
+ usernameToken.addNonce();
+ } else {
+ usernameToken.setPasswordType(PASSWORD_TEXT);
+ usernameToken.setUserInfo(username, password);
+ }
+ }
+
+ public static Object convertDocumentToString(Document document) throws Exception {
+ if (document == null) {
+ return ErrorCreator.createError(StringUtils.fromString(EMPTY_XML_DOCUMENT_ERROR));
+ }
+ Transformer transformer = TransformerFactory.newInstance().newTransformer();
+ StringWriter writer = new StringWriter();
+ transformer.transform(new DOMSource(document), new StreamResult(writer));
+ return StringUtils.fromString(writer.toString());
+ }
+}
diff --git a/native/src/main/resources/META-INF/native-image/io.ballerina.stdlib/soap-native/reflect-config.json b/native/src/main/resources/META-INF/native-image/io.ballerina.stdlib/soap-native/reflect-config.json
new file mode 100644
index 0000000..3cfa8bd
--- /dev/null
+++ b/native/src/main/resources/META-INF/native-image/io.ballerina.stdlib/soap-native/reflect-config.json
@@ -0,0 +1,17 @@
+[
+{
+ "name":"org.apache.commons.pool.impl.EvictionTimer"
+},
+{
+ "name":"org.apache.wss4j.dom.transform.AttachmentCiphertextTransform",
+ "methods":[{"name":"","parameterTypes":[] }]
+},
+{
+ "name":"org.apache.xml.security.c14n.implementations.CanonicalizerPhysical",
+ "methods":[{"name":"","parameterTypes":[] }]
+},
+{
+ "name":"org.apache.xml.security.utils.XMLUtils",
+ "fields":[{"name":"ignoreLineBreaks"}]
+}
+]
diff --git a/native/src/main/resources/META-INF/native-image/io.ballerina.stdlib/soap-native/resource-config.json b/native/src/main/resources/META-INF/native-image/io.ballerina.stdlib/soap-native/resource-config.json
new file mode 100644
index 0000000..f9fda78
--- /dev/null
+++ b/native/src/main/resources/META-INF/native-image/io.ballerina.stdlib/soap-native/resource-config.json
@@ -0,0 +1,13 @@
+{
+ "resources":{
+ "includes":[{
+ "pattern":"\\QMETA-INF/services/org.slf4j.spi.SLF4JServiceProvider\\E"
+ }]},
+ "bundles":[{
+ "name":"messages.wss4j_errors",
+ "locales":[""]
+ }, {
+ "name":"org/apache/xml/security/resource/xmlsecurity",
+ "locales":["en"]
+ }]
+}
diff --git a/settings.gradle b/settings.gradle
index 6dbaa0b..487afe6 100644
--- a/settings.gradle
+++ b/settings.gradle
@@ -21,9 +21,11 @@ plugins {
}
include ':checkstyle'
+include ':soap-native'
include ':soap-ballerina'
project(':checkstyle').projectDir = file("build-config${File.separator}checkstyle")
+project(':soap-native').projectDir = file('native')
project(':soap-ballerina').projectDir = file('ballerina')
gradleEnterprise {