From 781d963711a8971688f21d3d74cdd125eb4834f6 Mon Sep 17 00:00:00 2001 From: ayeshLK Date: Sat, 15 Jun 2024 19:48:17 +0530 Subject: [PATCH 1/6] Add support to directly provide crypto:PrivateKey and crypto:PublicKey in JWT signature configurations --- ballerina/jwt_issuer.bal | 6 ++++-- ballerina/jwt_validator.bal | 13 +++++++++---- 2 files changed, 13 insertions(+), 6 deletions(-) diff --git a/ballerina/jwt_issuer.bal b/ballerina/jwt_issuer.bal index 6f246e3b..4d05cf7e 100644 --- a/ballerina/jwt_issuer.bal +++ b/ballerina/jwt_issuer.bal @@ -41,7 +41,7 @@ public type IssuerConfig record {| # Represents JWT signature configurations. # # + algorithm - Cryptographic signing algorithm for JWS -# + config - KeyStore configurations, private key configurations or shared key configurations +# + config - KeyStore configurations, private key configurations, `crypto:PrivateKey` or shared key configurations public type IssuerSignatureConfig record {| SigningAlgorithm algorithm = RS256; record {| @@ -51,7 +51,7 @@ public type IssuerSignatureConfig record {| |} | record {| string keyFile; string keyPassword?; - |} | string config?; + |} | crypto:PrivateKey | string config?; |}; # Issues a JWT based on the provided configurations. JWT will be signed (JWS) if `crypto:KeyStore` information is @@ -93,6 +93,8 @@ public isolated function issue(IssuerConfig issuerConfig) returns string|Error { } else { return prepareError("Failed to decode private key.", privateKey); } + } else if config is crypto:PrivateKey { + return signJwtAssertion(jwtAssertion, algorithm, config); } else { string keyFile = config?.keyFile; string? keyPassword = config?.keyPassword; diff --git a/ballerina/jwt_validator.bal b/ballerina/jwt_validator.bal index 2326a463..9356ac62 100644 --- a/ballerina/jwt_validator.bal +++ b/ballerina/jwt_validator.bal @@ -48,7 +48,7 @@ public type ValidatorConfig record { # Represents JWT signature configurations. # # + jwksConfig - JWKS configurations -# + certFile - Public certificate file path +# + certFile - Public certificate file path or a `crypto:PublicKey` # + trustStoreConfig - JWT TrustStore configurations # + secret - HMAC secret configuration public type ValidatorSignatureConfig record {| @@ -57,7 +57,7 @@ public type ValidatorSignatureConfig record {| cache:CacheConfig cacheConfig?; ClientConfiguration clientConfig = {}; |} jwksConfig?; - string certFile?; + string|crypto:PublicKey certFile?; record {| crypto:TrustStore trustStore; string certAlias; @@ -331,8 +331,13 @@ isolated function validateSignature(string jwt, Header header, Payload payload, } else { return prepareError("Key ID (kid) is not provided in JOSE header."); } - } else if certFile is string { - crypto:PublicKey|crypto:Error publicKey = crypto:decodeRsaPublicKeyFromCertFile(certFile); + } else if certFile !is () { + crypto:PublicKey|crypto:Error publicKey; + if certFile is crypto:PublicKey { + publicKey = certFile; + } else { + publicKey = crypto:decodeRsaPublicKeyFromCertFile(certFile); + } if publicKey is crypto:PublicKey { if !validateCertificate(publicKey) { return prepareError("Public key certificate validity period has passed."); From 2ed150fca498cb3c615371bb7e7b95136b225ba5 Mon Sep 17 00:00:00 2001 From: ayeshLK Date: Sat, 15 Jun 2024 19:48:28 +0530 Subject: [PATCH 2/6] Add test cases --- ballerina/tests/jwt_issuer_test.bal | 40 ++++++++++++++++++++++++++ ballerina/tests/jwt_validator_test.bal | 18 ++++++++++++ 2 files changed, 58 insertions(+) diff --git a/ballerina/tests/jwt_issuer_test.bal b/ballerina/tests/jwt_issuer_test.bal index f3719c48..bfc17858 100644 --- a/ballerina/tests/jwt_issuer_test.bal +++ b/ballerina/tests/jwt_issuer_test.bal @@ -16,6 +16,8 @@ // NOTE: All the tokens/credentials used in this test are dummy tokens/credentials and used only for testing purposes. +import ballerina/crypto; +import ballerina/io; import ballerina/lang.'string; import ballerina/test; @@ -365,6 +367,44 @@ isolated function testIssueJwtWithEncryptedPrivateKey() returns Error? { assertDecodedJwt(result, expectedHeader, expectedPayload); } +@test:Config {} +isolated function testIssueJwtWithCryptoPrivateKey() returns io:Error|crypto:Error|Error? { + byte[] privateKeyContent = check io:fileReadBytes(PRIVATE_KEY_PATH); + crypto:PrivateKey privateKey = check crypto:decodeRsaPrivateKeyFromContent(privateKeyContent); + IssuerConfig issuerConfig = { + username: "John", + issuer: "wso2", + audience: ["ballerina", "ballerinaSamples"], + expTime: 600, + signatureConfig: { + config: privateKey + } + }; + string result = check issue(issuerConfig); + string expectedHeader = "{\"alg\":\"RS256\", \"typ\":\"JWT\"}"; + string expectedPayload = "{\"iss\":\"wso2\", \"sub\":\"John\", \"aud\":[\"ballerina\", \"ballerinaSamples\"]"; + assertDecodedJwt(result, expectedHeader, expectedPayload); +} + +@test:Config {} +isolated function testIssueJwtWithEncryptedCryptoPrivateKey() returns io:Error|crypto:Error|Error? { + byte[] privateKeyContent = check io:fileReadBytes(ENCRYPTED_PRIVATE_KEY_PATH); + crypto:PrivateKey encryptedPrivateKey = check crypto:decodeRsaPrivateKeyFromContent(privateKeyContent, "ballerina"); + IssuerConfig issuerConfig = { + username: "John", + issuer: "wso2", + audience: ["ballerina", "ballerinaSamples"], + expTime: 600, + signatureConfig: { + config: encryptedPrivateKey + } + }; + string result = check issue(issuerConfig); + string expectedHeader = "{\"alg\":\"RS256\", \"typ\":\"JWT\"}"; + string expectedPayload = "{\"iss\":\"wso2\", \"sub\":\"John\", \"aud\":[\"ballerina\", \"ballerinaSamples\"]"; + assertDecodedJwt(result, expectedHeader, expectedPayload); +} + isolated function assertDecodedJwt(string jwt, string header, string payload) { string[] parts = re `\.`.split(jwt); // check header diff --git a/ballerina/tests/jwt_validator_test.bal b/ballerina/tests/jwt_validator_test.bal index e90306c6..419837d6 100644 --- a/ballerina/tests/jwt_validator_test.bal +++ b/ballerina/tests/jwt_validator_test.bal @@ -16,6 +16,8 @@ // NOTE: All the tokens/credentials used in this test are dummy tokens/credentials and used only for testing purposes. +import ballerina/crypto; +import ballerina/io; import ballerina/test; @test:Config {} @@ -693,6 +695,22 @@ isolated function testValidateJwtSignatureWithPublicCert() returns Error? { test:assertEquals(result?.iss, "wso2"); } +@test:Config {} +isolated function testValidateJwtSignatureWithCryptoPublicKey() returns io:Error|crypto:Error|Error? { + byte[] pubicCertContent = check io:fileReadBytes(PUBLIC_CERT_PATH); + crypto:PublicKey publicKey = check crypto:decodeRsaPublicKeyFromContent(pubicCertContent); + ValidatorConfig validatorConfig = { + issuer: "wso2", + audience: ["ballerina", "ballerinaSamples"], + clockSkew: 60, + signatureConfig: { + certFile: publicKey + } + }; + Payload result = check validate(JWT1, validatorConfig); + test:assertEquals(result?.iss, "wso2"); +} + @test:Config {} isolated function testValidateJwtSignatureWithInvalidPublicCert() { ValidatorConfig validatorConfig = { From 45b2093269948a2b1fc2ef3641ca390b9c19793b Mon Sep 17 00:00:00 2001 From: ayeshLK Date: Sat, 15 Jun 2024 19:48:58 +0530 Subject: [PATCH 3/6] Update change log --- changelog.md | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/changelog.md b/changelog.md index 2b979d08..c28abd26 100644 --- a/changelog.md +++ b/changelog.md @@ -5,6 +5,11 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), ## Unreleased +### Added +- [Add support to directly provide `crypto:PrivateKey` and `crypto:PublicKey` in JWT signature configurations](https://github.com/ballerina-platform/ballerina-library/issues/6514) + +## [2.12.1] - 2024-06-14 + ## Changed - [Revert support to directly provide `crypto:PrivateKey` and `crypto:PublicKey` in JWT signature configurations](https://github.com/ballerina-platform/ballerina-library/issues/6628) From 7b32d724a2ee8be5b7cf3fad69d799429f3b821b Mon Sep 17 00:00:00 2001 From: ayeshLK Date: Sat, 15 Jun 2024 19:51:10 +0530 Subject: [PATCH 4/6] Update package spec --- docs/spec/spec.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/docs/spec/spec.md b/docs/spec/spec.md index 9e99e157..f0e58d14 100644 --- a/docs/spec/spec.md +++ b/docs/spec/spec.md @@ -1,9 +1,9 @@ # Specification: Ballerina JWT Library -_Owners_: @ldclakmal @shafreenAnfar +_Authors_: @ldclakmal @shafreenAnfar @ayeshLK _Reviewers_: @shafreenAnfar _Created_: 2021/10/01 -_Updated_: 2022/02/17 +_Updated_: 2024/06/15 _Edition_: Swan Lake ## Introduction @@ -104,7 +104,7 @@ public type ValidatorSignatureConfig record {| cache:CacheConfig cacheConfig?; ClientConfiguration clientConfig = {}; |} jwksConfig?; - string certFile?; + string|crypto:PublicKey certFile?; record {| crypto:TrustStore trustStore; string certAlias; @@ -222,7 +222,7 @@ public type IssuerSignatureConfig record {| |}|record {| string keyFile; string keyPassword?; - |}|string config?; + |}|crypto:PrivateKey|string config?; |}; public class ClientSelfSignedJwtAuthProvider { From 0fd8cd3fa0b883bb483e755942c62aa1259c0521 Mon Sep 17 00:00:00 2001 From: ayeshLK Date: Sat, 15 Jun 2024 20:14:09 +0530 Subject: [PATCH 5/6] [Automated] Update the native jar versions --- ballerina/Ballerina.toml | 6 +++--- ballerina/Dependencies.toml | 6 +++++- 2 files changed, 8 insertions(+), 4 deletions(-) diff --git a/ballerina/Ballerina.toml b/ballerina/Ballerina.toml index f8a15885..b223b021 100644 --- a/ballerina/Ballerina.toml +++ b/ballerina/Ballerina.toml @@ -1,7 +1,7 @@ [package] org = "ballerina" name = "jwt" -version = "2.12.1" +version = "3.0.0" authors = ["Ballerina"] keywords = ["security", "authentication", "jwt", "jwk", "jws"] repository = "https://github.com/ballerina-platform/module-ballerina-jwt" @@ -15,5 +15,5 @@ graalvmCompatible = true [[platform.java17.dependency]] groupId = "io.ballerina.stdlib" artifactId = "jwt-native" -version = "2.12.1" -path = "../native/build/libs/jwt-native-2.12.1.jar" +version = "3.0.0" +path = "../native/build/libs/jwt-native-3.0.0-SNAPSHOT.jar" diff --git a/ballerina/Dependencies.toml b/ballerina/Dependencies.toml index 46b388ea..b6b22a79 100644 --- a/ballerina/Dependencies.toml +++ b/ballerina/Dependencies.toml @@ -49,6 +49,9 @@ dependencies = [ {org = "ballerina", name = "jballerina.java"}, {org = "ballerina", name = "lang.value"} ] +modules = [ + {org = "ballerina", packageName = "io", moduleName = "io"} +] [[package]] org = "ballerina" @@ -61,10 +64,11 @@ modules = [ [[package]] org = "ballerina" name = "jwt" -version = "2.12.1" +version = "3.0.0" dependencies = [ {org = "ballerina", name = "cache"}, {org = "ballerina", name = "crypto"}, + {org = "ballerina", name = "io"}, {org = "ballerina", name = "jballerina.java"}, {org = "ballerina", name = "lang.int"}, {org = "ballerina", name = "lang.string"}, From 11ded37243158737136b0529888824308b42db00 Mon Sep 17 00:00:00 2001 From: ayeshLK Date: Sat, 15 Jun 2024 20:14:47 +0530 Subject: [PATCH 6/6] Update package version --- gradle.properties | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle.properties b/gradle.properties index c3c11fd1..b4504072 100644 --- a/gradle.properties +++ b/gradle.properties @@ -1,6 +1,6 @@ org.gradle.caching=true group=io.ballerina.stdlib -version=2.12.2-SNAPSHOT +version=3.0.0-SNAPSHOT puppycrawlCheckstyleVersion=10.12.0 ballerinaGradlePluginVersion=2.0.1