From 6ac9f1ae491684a294166c20939a3c6f89623654 Mon Sep 17 00:00:00 2001 From: siujamo Date: Mon, 9 Jun 2025 11:49:27 +0800 Subject: [PATCH 1/7] refactor: renamed key pair loader --- .../security/impl/{EcKeyLoader.java => ECKeyLoader.java} | 4 ++-- .../security/impl/{RsaKeyLoader.java => RSAKeyLoader.java} | 4 ++-- .../onixbyte/simplejwt/authzero/AuthzeroTokenResolver.java | 6 ++---- 3 files changed, 6 insertions(+), 8 deletions(-) rename key-pair-loader/src/main/java/com/onixbyte/security/impl/{EcKeyLoader.java => ECKeyLoader.java} (98%) rename key-pair-loader/src/main/java/com/onixbyte/security/impl/{RsaKeyLoader.java => RSAKeyLoader.java} (98%) diff --git a/key-pair-loader/src/main/java/com/onixbyte/security/impl/EcKeyLoader.java b/key-pair-loader/src/main/java/com/onixbyte/security/impl/ECKeyLoader.java similarity index 98% rename from key-pair-loader/src/main/java/com/onixbyte/security/impl/EcKeyLoader.java rename to key-pair-loader/src/main/java/com/onixbyte/security/impl/ECKeyLoader.java index 96daa58..cdb2124 100644 --- a/key-pair-loader/src/main/java/com/onixbyte/security/impl/EcKeyLoader.java +++ b/key-pair-loader/src/main/java/com/onixbyte/security/impl/ECKeyLoader.java @@ -53,7 +53,7 @@ import java.util.Base64; * @version 2.0.0 * @since 2.0.0 */ -public class EcKeyLoader implements KeyLoader { +public class ECKeyLoader implements KeyLoader { private final KeyFactory keyFactory; @@ -62,7 +62,7 @@ public class EcKeyLoader implements KeyLoader { /** * Initialise a key loader for EC-based algorithms. */ - public EcKeyLoader() { + public ECKeyLoader() { try { this.keyFactory = KeyFactory.getInstance("EC"); this.decoder = Base64.getDecoder(); diff --git a/key-pair-loader/src/main/java/com/onixbyte/security/impl/RsaKeyLoader.java b/key-pair-loader/src/main/java/com/onixbyte/security/impl/RSAKeyLoader.java similarity index 98% rename from key-pair-loader/src/main/java/com/onixbyte/security/impl/RsaKeyLoader.java rename to key-pair-loader/src/main/java/com/onixbyte/security/impl/RSAKeyLoader.java index 7f1b6e4..9520867 100644 --- a/key-pair-loader/src/main/java/com/onixbyte/security/impl/RsaKeyLoader.java +++ b/key-pair-loader/src/main/java/com/onixbyte/security/impl/RSAKeyLoader.java @@ -44,7 +44,7 @@ import java.util.Base64; * @see KeyLoader * @see KeyLoadingException */ -public class RsaKeyLoader implements KeyLoader { +public class RSAKeyLoader implements KeyLoader { private final Base64.Decoder decoder; private final KeyFactory keyFactory; @@ -55,7 +55,7 @@ public class RsaKeyLoader implements KeyLoader { * This constructor initialises the Base64 decoder and the RSA {@link KeyFactory}. It may throw * a {@link KeyLoadingException} if the RSA algorithm is not available. */ - public RsaKeyLoader() { + public RSAKeyLoader() { try { this.decoder = Base64.getDecoder(); this.keyFactory = KeyFactory.getInstance("RSA"); diff --git a/simple-jwt-authzero/src/main/java/com/onixbyte/simplejwt/authzero/AuthzeroTokenResolver.java b/simple-jwt-authzero/src/main/java/com/onixbyte/simplejwt/authzero/AuthzeroTokenResolver.java index aa5f2d3..790700c 100644 --- a/simple-jwt-authzero/src/main/java/com/onixbyte/simplejwt/authzero/AuthzeroTokenResolver.java +++ b/simple-jwt-authzero/src/main/java/com/onixbyte/simplejwt/authzero/AuthzeroTokenResolver.java @@ -19,8 +19,7 @@ package com.onixbyte.simplejwt.authzero; import com.onixbyte.devkit.utils.Base64Util; import com.onixbyte.guid.GuidCreator; -import com.onixbyte.security.KeyLoader; -import com.onixbyte.security.impl.EcKeyLoader; +import com.onixbyte.security.impl.ECKeyLoader; import com.onixbyte.simplejwt.TokenPayload; import com.onixbyte.simplejwt.TokenResolver; import com.onixbyte.simplejwt.annotations.ExcludeFromPayload; @@ -43,7 +42,6 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; import java.lang.reflect.InvocationTargetException; -import java.security.NoSuchAlgorithmException; import java.security.interfaces.ECPrivateKey; import java.security.interfaces.ECPublicKey; import java.time.Duration; @@ -179,7 +177,7 @@ public class AuthzeroTokenResolver implements TokenResolver { * @return the builder instance */ public Builder keyPair(String publicKey, String privateKey) { - var keyLoader = new EcKeyLoader(); + var keyLoader = new ECKeyLoader(); this.publicKey = keyLoader.loadPublicKey(publicKey); this.privateKey = keyLoader.loadPrivateKey(privateKey); return this; From e03cc180c9a97b4a7d69b329bb1583e68ea0f58a Mon Sep 17 00:00:00 2001 From: siujamo Date: Mon, 9 Jun 2025 11:49:57 +0800 Subject: [PATCH 2/7] feat: load RSA public key via modulus and exponent --- .../java/com/onixbyte/security/KeyLoader.java | 7 ++++++ .../onixbyte/security/impl/RSAKeyLoader.java | 24 +++++++++++++++++++ 2 files changed, 31 insertions(+) diff --git a/key-pair-loader/src/main/java/com/onixbyte/security/KeyLoader.java b/key-pair-loader/src/main/java/com/onixbyte/security/KeyLoader.java index b299d5a..0c7a4a5 100644 --- a/key-pair-loader/src/main/java/com/onixbyte/security/KeyLoader.java +++ b/key-pair-loader/src/main/java/com/onixbyte/security/KeyLoader.java @@ -17,8 +17,11 @@ package com.onixbyte.security; +import com.onixbyte.security.exception.KeyLoadingException; + import java.security.PrivateKey; import java.security.PublicKey; +import java.security.interfaces.RSAPublicKey; /** * The {@code KeyLoader} class provides utility methods for loading keys pairs from PEM-formatted @@ -49,6 +52,10 @@ public interface KeyLoader { */ PublicKey loadPublicKey(String pemKeyText); + default RSAPublicKey loadPublicKey(String modulus, String exponent) { + throw new KeyLoadingException("This key loader does not support RSA Public key loading."); + } + /** * Retrieves the raw content of a PEM formatted key by removing unnecessary headers, footers, * and new line characters. diff --git a/key-pair-loader/src/main/java/com/onixbyte/security/impl/RSAKeyLoader.java b/key-pair-loader/src/main/java/com/onixbyte/security/impl/RSAKeyLoader.java index 9520867..b0f5dc2 100644 --- a/key-pair-loader/src/main/java/com/onixbyte/security/impl/RSAKeyLoader.java +++ b/key-pair-loader/src/main/java/com/onixbyte/security/impl/RSAKeyLoader.java @@ -20,12 +20,14 @@ package com.onixbyte.security.impl; import com.onixbyte.security.KeyLoader; import com.onixbyte.security.exception.KeyLoadingException; +import java.math.BigInteger; import java.security.KeyFactory; import java.security.NoSuchAlgorithmException; import java.security.interfaces.RSAPrivateKey; import java.security.interfaces.RSAPublicKey; import java.security.spec.InvalidKeySpecException; import java.security.spec.PKCS8EncodedKeySpec; +import java.security.spec.RSAPublicKeySpec; import java.security.spec.X509EncodedKeySpec; import java.util.Base64; @@ -47,6 +49,9 @@ import java.util.Base64; public class RSAKeyLoader implements KeyLoader { private final Base64.Decoder decoder; + + private final Base64.Decoder urlDecoder; + private final KeyFactory keyFactory; /** @@ -58,6 +63,7 @@ public class RSAKeyLoader implements KeyLoader { public RSAKeyLoader() { try { this.decoder = Base64.getDecoder(); + this.urlDecoder = Base64.getUrlDecoder(); this.keyFactory = KeyFactory.getInstance("RSA"); } catch (NoSuchAlgorithmException e) { throw new KeyLoadingException(e); @@ -133,4 +139,22 @@ public class RSAKeyLoader implements KeyLoader { throw new KeyLoadingException("Key spec is invalid.", e); } } + + @Override + public RSAPublicKey loadPublicKey(String modulus, String exponent) { + try { + var _modulus = new BigInteger(1, urlDecoder.decode(modulus)); + var _exponent = new BigInteger(1, urlDecoder.decode(exponent)); + + var keySpec = new RSAPublicKeySpec(_modulus, _exponent); + var kf = KeyFactory.getInstance("RSA"); + if (kf.generatePublic(keySpec) instanceof RSAPublicKey rsaPublicKey) { + return rsaPublicKey; + } else { + throw new KeyLoadingException("Cannot generate RSA public key with given modulus and exponent."); + } + } catch (NoSuchAlgorithmException | InvalidKeySpecException e) { + throw new KeyLoadingException("Cannot generate RSA public key with given modulus and exponent.", e); + } + } } From a4d42722ad40e3f85397334f718bc86781b542a5 Mon Sep 17 00:00:00 2001 From: siujamo Date: Mon, 9 Jun 2025 11:52:12 +0800 Subject: [PATCH 3/7] docs: add docs for loading RSA public key with modulus and exponent --- .../main/java/com/onixbyte/security/KeyLoader.java | 11 +++++++++++ .../com/onixbyte/security/impl/RSAKeyLoader.java | 14 ++++++++++---- 2 files changed, 21 insertions(+), 4 deletions(-) diff --git a/key-pair-loader/src/main/java/com/onixbyte/security/KeyLoader.java b/key-pair-loader/src/main/java/com/onixbyte/security/KeyLoader.java index 0c7a4a5..a367ac1 100644 --- a/key-pair-loader/src/main/java/com/onixbyte/security/KeyLoader.java +++ b/key-pair-loader/src/main/java/com/onixbyte/security/KeyLoader.java @@ -19,9 +19,11 @@ package com.onixbyte.security; import com.onixbyte.security.exception.KeyLoadingException; +import java.security.KeyFactory; import java.security.PrivateKey; import java.security.PublicKey; import java.security.interfaces.RSAPublicKey; +import java.security.spec.KeySpec; /** * The {@code KeyLoader} class provides utility methods for loading keys pairs from PEM-formatted @@ -52,6 +54,15 @@ public interface KeyLoader { */ PublicKey loadPublicKey(String pemKeyText); + /** + * Get the public key with given modulus and public exponent. + * + * @param modulus the modulus + * @param exponent the public exponent + * @return generated public key object from the provided key specification + * @see KeyFactory#getInstance(String) + * @see KeyFactory#generatePublic(KeySpec) + */ default RSAPublicKey loadPublicKey(String modulus, String exponent) { throw new KeyLoadingException("This key loader does not support RSA Public key loading."); } diff --git a/key-pair-loader/src/main/java/com/onixbyte/security/impl/RSAKeyLoader.java b/key-pair-loader/src/main/java/com/onixbyte/security/impl/RSAKeyLoader.java index b0f5dc2..3cf9324 100644 --- a/key-pair-loader/src/main/java/com/onixbyte/security/impl/RSAKeyLoader.java +++ b/key-pair-loader/src/main/java/com/onixbyte/security/impl/RSAKeyLoader.java @@ -25,10 +25,7 @@ import java.security.KeyFactory; import java.security.NoSuchAlgorithmException; import java.security.interfaces.RSAPrivateKey; import java.security.interfaces.RSAPublicKey; -import java.security.spec.InvalidKeySpecException; -import java.security.spec.PKCS8EncodedKeySpec; -import java.security.spec.RSAPublicKeySpec; -import java.security.spec.X509EncodedKeySpec; +import java.security.spec.*; import java.util.Base64; /** @@ -140,6 +137,15 @@ public class RSAKeyLoader implements KeyLoader { } } + /** + * Get the public key with given modulus and public exponent. + * + * @param modulus the modulus + * @param exponent the public exponent + * @return generated public key object from the provided key specification + * @see KeyFactory#getInstance(String) + * @see KeyFactory#generatePublic(KeySpec) + */ @Override public RSAPublicKey loadPublicKey(String modulus, String exponent) { try { From 7dfd02f11e7fd24ad0a7494050a62fbd359528ed Mon Sep 17 00:00:00 2001 From: siujamo Date: Mon, 9 Jun 2025 12:00:43 +0800 Subject: [PATCH 4/7] refactor: fix exception message typo --- .../src/main/java/com/onixbyte/security/KeyLoader.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/key-pair-loader/src/main/java/com/onixbyte/security/KeyLoader.java b/key-pair-loader/src/main/java/com/onixbyte/security/KeyLoader.java index a367ac1..8f3be18 100644 --- a/key-pair-loader/src/main/java/com/onixbyte/security/KeyLoader.java +++ b/key-pair-loader/src/main/java/com/onixbyte/security/KeyLoader.java @@ -64,7 +64,7 @@ public interface KeyLoader { * @see KeyFactory#generatePublic(KeySpec) */ default RSAPublicKey loadPublicKey(String modulus, String exponent) { - throw new KeyLoadingException("This key loader does not support RSA Public key loading."); + throw new KeyLoadingException("This key loader does not support loading an RSA public key."); } /** From 4965a9529772e67d461c4ec6e1ce14c397a95e9c Mon Sep 17 00:00:00 2001 From: siujamo Date: Mon, 9 Jun 2025 12:40:29 +0800 Subject: [PATCH 5/7] feat: load EC public key with given x, y and curve name --- .../java/com/onixbyte/security/KeyLoader.java | 5 ++ .../onixbyte/security/impl/ECKeyLoader.java | 49 +++++++++++++++++-- 2 files changed, 51 insertions(+), 3 deletions(-) diff --git a/key-pair-loader/src/main/java/com/onixbyte/security/KeyLoader.java b/key-pair-loader/src/main/java/com/onixbyte/security/KeyLoader.java index 8f3be18..7464a9d 100644 --- a/key-pair-loader/src/main/java/com/onixbyte/security/KeyLoader.java +++ b/key-pair-loader/src/main/java/com/onixbyte/security/KeyLoader.java @@ -22,6 +22,7 @@ import com.onixbyte.security.exception.KeyLoadingException; import java.security.KeyFactory; import java.security.PrivateKey; import java.security.PublicKey; +import java.security.interfaces.ECPublicKey; import java.security.interfaces.RSAPublicKey; import java.security.spec.KeySpec; @@ -67,6 +68,10 @@ public interface KeyLoader { throw new KeyLoadingException("This key loader does not support loading an RSA public key."); } + default ECPublicKey loadPublicKey(String xHex, String yHex, String curveName) { + throw new KeyLoadingException("This key loader does not support loading an EC public key."); + } + /** * Retrieves the raw content of a PEM formatted key by removing unnecessary headers, footers, * and new line characters. diff --git a/key-pair-loader/src/main/java/com/onixbyte/security/impl/ECKeyLoader.java b/key-pair-loader/src/main/java/com/onixbyte/security/impl/ECKeyLoader.java index cdb2124..ffc45ef 100644 --- a/key-pair-loader/src/main/java/com/onixbyte/security/impl/ECKeyLoader.java +++ b/key-pair-loader/src/main/java/com/onixbyte/security/impl/ECKeyLoader.java @@ -20,14 +20,16 @@ package com.onixbyte.security.impl; import com.onixbyte.security.KeyLoader; import com.onixbyte.security.exception.KeyLoadingException; +import java.math.BigInteger; +import java.security.AlgorithmParameters; import java.security.KeyFactory; import java.security.NoSuchAlgorithmException; import java.security.interfaces.ECPrivateKey; import java.security.interfaces.ECPublicKey; -import java.security.spec.InvalidKeySpecException; -import java.security.spec.PKCS8EncodedKeySpec; -import java.security.spec.X509EncodedKeySpec; +import java.security.spec.*; import java.util.Base64; +import java.util.HashSet; +import java.util.Set; /** * Key pair loader for loading key pairs for ECDSA-based algorithms. @@ -59,6 +61,13 @@ public class ECKeyLoader implements KeyLoader { private final Base64.Decoder decoder; + /** + * Supported curves. + */ + public static final Set SUPPORTED_CURVES = new HashSet<>(Set.of( + "secp256r1", "secp384r1", "secp521r1", "secp224r1" + )); + /** * Initialise a key loader for EC-based algorithms. */ @@ -122,4 +131,38 @@ public class ECKeyLoader implements KeyLoader { } } + @Override + public ECPublicKey loadPublicKey(String xHex, String yHex, String curveName) { + if (!SUPPORTED_CURVES.contains(curveName)) { + throw new KeyLoadingException("Given curve is not supported yet."); + } + + try { + // Convert hex string coordinates to BigInteger + var x = new BigInteger(xHex, 16); + var y = new BigInteger(yHex, 16); + + // Create ECPoint with (x, y) + var ecPoint = new ECPoint(x, y); + + // Get EC parameter spec for the named curve + var parameters = AlgorithmParameters.getInstance("EC"); + parameters.init(new ECGenParameterSpec(curveName)); + var ecParameterSpec = parameters.getParameterSpec(ECParameterSpec.class); + + // Create ECPublicKeySpec with point and curve params + var pubSpec = new ECPublicKeySpec(ecPoint, ecParameterSpec); + + // Generate public key using KeyFactory + var publicKey = keyFactory.generatePublic(pubSpec); + + if (publicKey instanceof ECPublicKey ecPublicKey) { + return ecPublicKey; + } else { + throw new KeyLoadingException("Cannot load EC public key with given x, y and curve name."); + } + } catch (NoSuchAlgorithmException | InvalidParameterSpecException | InvalidKeySpecException e) { + throw new KeyLoadingException("Cannot load EC public key with given x, y and curve name.", e); + } + } } From 003cb5a086bcaed9c764cdb47b43abe30ddb5671 Mon Sep 17 00:00:00 2001 From: siujamo Date: Mon, 9 Jun 2025 12:51:21 +0800 Subject: [PATCH 6/7] docs: add javadoc --- .../java/com/onixbyte/security/KeyLoader.java | 13 +++++++++++++ .../com/onixbyte/security/impl/ECKeyLoader.java | 17 +++++++++++++++++ 2 files changed, 30 insertions(+) diff --git a/key-pair-loader/src/main/java/com/onixbyte/security/KeyLoader.java b/key-pair-loader/src/main/java/com/onixbyte/security/KeyLoader.java index 7464a9d..d8806a0 100644 --- a/key-pair-loader/src/main/java/com/onixbyte/security/KeyLoader.java +++ b/key-pair-loader/src/main/java/com/onixbyte/security/KeyLoader.java @@ -68,6 +68,19 @@ public interface KeyLoader { throw new KeyLoadingException("This key loader does not support loading an RSA public key."); } + /** + * Loads an EC public key using the provided x and y coordinates together with the curve name. + *

+ * This default implementation throws a {@link KeyLoadingException} to signify that this key loader does not support + * loading an EC public key. Implementing classes are expected to override this method to supply their own + * loading logic. + * + * @param xHex the hexadecimal string representing the x coordinate of the EC point + * @param yHex the hexadecimal string representing the y coordinate of the EC point + * @param curveName the name of the elliptic curve + * @return the loaded {@link ECPublicKey} instance + * @throws KeyLoadingException if loading is not supported or fails + */ default ECPublicKey loadPublicKey(String xHex, String yHex, String curveName) { throw new KeyLoadingException("This key loader does not support loading an EC public key."); } diff --git a/key-pair-loader/src/main/java/com/onixbyte/security/impl/ECKeyLoader.java b/key-pair-loader/src/main/java/com/onixbyte/security/impl/ECKeyLoader.java index ffc45ef..e482c0f 100644 --- a/key-pair-loader/src/main/java/com/onixbyte/security/impl/ECKeyLoader.java +++ b/key-pair-loader/src/main/java/com/onixbyte/security/impl/ECKeyLoader.java @@ -131,6 +131,23 @@ public class ECKeyLoader implements KeyLoader { } } + /** + * Loads an EC public key from the given hexadecimal x and y coordinates alongside the curve name. + *

+ * This method converts the hexadecimal string representations of the EC point coordinates into {@link BigInteger} + * instances, then constructs an {@link ECPoint} and retrieves the corresponding {@link ECParameterSpec} for the + * named curve. Subsequently, it utilises the {@link KeyFactory} to generate an {@link ECPublicKey}. + *

+ * Only curves listed in {@link #SUPPORTED_CURVES} are supported. Should the specified curve name be unsupported, + * or if key construction fails due to invalid parameters or unsupported algorithms, a {@link KeyLoadingException} + * will be thrown. + * + * @param xHex the hexadecimal string representing the x-coordinate of the EC point + * @param yHex the hexadecimal string representing the y-coordinate of the EC point + * @param curveName the name of the elliptic curve + * @return the {@link ECPublicKey} generated from the specified coordinates and curve + * @throws KeyLoadingException if the curve is unsupported or key generation fails + */ @Override public ECPublicKey loadPublicKey(String xHex, String yHex, String curveName) { if (!SUPPORTED_CURVES.contains(curveName)) { From 8f844457134893568a4770b451f1717b017f565c Mon Sep 17 00:00:00 2001 From: siujamo Date: Mon, 9 Jun 2025 12:52:11 +0800 Subject: [PATCH 7/7] docs: change javadoc expressions --- .../java/com/onixbyte/security/KeyLoader.java | 17 +++++++++++------ 1 file changed, 11 insertions(+), 6 deletions(-) diff --git a/key-pair-loader/src/main/java/com/onixbyte/security/KeyLoader.java b/key-pair-loader/src/main/java/com/onixbyte/security/KeyLoader.java index d8806a0..3bb267c 100644 --- a/key-pair-loader/src/main/java/com/onixbyte/security/KeyLoader.java +++ b/key-pair-loader/src/main/java/com/onixbyte/security/KeyLoader.java @@ -56,13 +56,18 @@ public interface KeyLoader { PublicKey loadPublicKey(String pemKeyText); /** - * Get the public key with given modulus and public exponent. + * Loads an RSA public key using the provided modulus and exponent. + *

+ * This default implementation throws a {@link KeyLoadingException} to signify that this key loader does not support + * loading an RSA public key. Implementing classes are expected to override this method to supply their own + * loading logic. * - * @param modulus the modulus - * @param exponent the public exponent - * @return generated public key object from the provided key specification - * @see KeyFactory#getInstance(String) - * @see KeyFactory#generatePublic(KeySpec) + * @param modulus the modulus value of the RSA public key, usually represented in hexadecimal or Base64 + * string format + * @param exponent the public exponent value of the RSA public key, usually represented in hexadecimal or Base64 + * string format + * @return the loaded {@link RSAPublicKey} instance + * @throws KeyLoadingException if loading is not supported or fails */ default RSAPublicKey loadPublicKey(String modulus, String exponent) { throw new KeyLoadingException("This key loader does not support loading an RSA public key.");