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..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 @@ -17,8 +17,14 @@ 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.ECPublicKey; +import java.security.interfaces.RSAPublicKey; +import java.security.spec.KeySpec; /** * The {@code KeyLoader} class provides utility methods for loading keys pairs from PEM-formatted @@ -49,6 +55,41 @@ public interface KeyLoader { */ PublicKey loadPublicKey(String pemKeyText); + /** + * 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 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."); + } + + /** + * 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."); + } + /** * 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 similarity index 57% 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..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 @@ -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. @@ -53,16 +55,23 @@ 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; 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. */ - public EcKeyLoader() { + public ECKeyLoader() { try { this.keyFactory = KeyFactory.getInstance("EC"); this.decoder = Base64.getDecoder(); @@ -122,4 +131,55 @@ 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)) { + 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); + } + } } 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 78% 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..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 @@ -20,13 +20,12 @@ 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.X509EncodedKeySpec; +import java.security.spec.*; import java.util.Base64; /** @@ -44,9 +43,12 @@ 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 Base64.Decoder urlDecoder; + private final KeyFactory keyFactory; /** @@ -55,9 +57,10 @@ 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.urlDecoder = Base64.getUrlDecoder(); this.keyFactory = KeyFactory.getInstance("RSA"); } catch (NoSuchAlgorithmException e) { throw new KeyLoadingException(e); @@ -133,4 +136,31 @@ public class RsaKeyLoader implements KeyLoader { throw new KeyLoadingException("Key spec is invalid.", e); } } + + /** + * 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 { + 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); + } + } } 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;