Skip to content

Commit 81ade8f

Browse files
author
Zihlu Wang
authored
Merge pull request #68 from onixbyte/feature/key-loader
feat: import key loading method
2 parents 1059dc7 + 8f84445 commit 81ade8f

File tree

4 files changed

+143
-14
lines changed

4 files changed

+143
-14
lines changed

key-pair-loader/src/main/java/com/onixbyte/security/KeyLoader.java

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,8 +17,14 @@
1717

1818
package com.onixbyte.security;
1919

20+
import com.onixbyte.security.exception.KeyLoadingException;
21+
22+
import java.security.KeyFactory;
2023
import java.security.PrivateKey;
2124
import java.security.PublicKey;
25+
import java.security.interfaces.ECPublicKey;
26+
import java.security.interfaces.RSAPublicKey;
27+
import java.security.spec.KeySpec;
2228

2329
/**
2430
* The {@code KeyLoader} class provides utility methods for loading keys pairs from PEM-formatted
@@ -49,6 +55,41 @@ public interface KeyLoader {
4955
*/
5056
PublicKey loadPublicKey(String pemKeyText);
5157

58+
/**
59+
* Loads an RSA public key using the provided modulus and exponent.
60+
* <p>
61+
* This default implementation throws a {@link KeyLoadingException} to signify that this key loader does not support
62+
* loading an RSA public key. Implementing classes are expected to override this method to supply their own
63+
* loading logic.
64+
*
65+
* @param modulus the modulus value of the RSA public key, usually represented in hexadecimal or Base64
66+
* string format
67+
* @param exponent the public exponent value of the RSA public key, usually represented in hexadecimal or Base64
68+
* string format
69+
* @return the loaded {@link RSAPublicKey} instance
70+
* @throws KeyLoadingException if loading is not supported or fails
71+
*/
72+
default RSAPublicKey loadPublicKey(String modulus, String exponent) {
73+
throw new KeyLoadingException("This key loader does not support loading an RSA public key.");
74+
}
75+
76+
/**
77+
* Loads an EC public key using the provided x and y coordinates together with the curve name.
78+
* <p>
79+
* This default implementation throws a {@link KeyLoadingException} to signify that this key loader does not support
80+
* loading an EC public key. Implementing classes are expected to override this method to supply their own
81+
* loading logic.
82+
*
83+
* @param xHex the hexadecimal string representing the x coordinate of the EC point
84+
* @param yHex the hexadecimal string representing the y coordinate of the EC point
85+
* @param curveName the name of the elliptic curve
86+
* @return the loaded {@link ECPublicKey} instance
87+
* @throws KeyLoadingException if loading is not supported or fails
88+
*/
89+
default ECPublicKey loadPublicKey(String xHex, String yHex, String curveName) {
90+
throw new KeyLoadingException("This key loader does not support loading an EC public key.");
91+
}
92+
5293
/**
5394
* Retrieves the raw content of a PEM formatted key by removing unnecessary headers, footers,
5495
* and new line characters.

key-pair-loader/src/main/java/com/onixbyte/security/impl/EcKeyLoader.java renamed to key-pair-loader/src/main/java/com/onixbyte/security/impl/ECKeyLoader.java

Lines changed: 65 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -20,14 +20,16 @@
2020
import com.onixbyte.security.KeyLoader;
2121
import com.onixbyte.security.exception.KeyLoadingException;
2222

23+
import java.math.BigInteger;
24+
import java.security.AlgorithmParameters;
2325
import java.security.KeyFactory;
2426
import java.security.NoSuchAlgorithmException;
2527
import java.security.interfaces.ECPrivateKey;
2628
import java.security.interfaces.ECPublicKey;
27-
import java.security.spec.InvalidKeySpecException;
28-
import java.security.spec.PKCS8EncodedKeySpec;
29-
import java.security.spec.X509EncodedKeySpec;
29+
import java.security.spec.*;
3030
import java.util.Base64;
31+
import java.util.HashSet;
32+
import java.util.Set;
3133

3234
/**
3335
* Key pair loader for loading key pairs for ECDSA-based algorithms.
@@ -53,16 +55,23 @@
5355
* @version 2.0.0
5456
* @since 2.0.0
5557
*/
56-
public class EcKeyLoader implements KeyLoader {
58+
public class ECKeyLoader implements KeyLoader {
5759

5860
private final KeyFactory keyFactory;
5961

6062
private final Base64.Decoder decoder;
6163

64+
/**
65+
* Supported curves.
66+
*/
67+
public static final Set<String> SUPPORTED_CURVES = new HashSet<>(Set.of(
68+
"secp256r1", "secp384r1", "secp521r1", "secp224r1"
69+
));
70+
6271
/**
6372
* Initialise a key loader for EC-based algorithms.
6473
*/
65-
public EcKeyLoader() {
74+
public ECKeyLoader() {
6675
try {
6776
this.keyFactory = KeyFactory.getInstance("EC");
6877
this.decoder = Base64.getDecoder();
@@ -122,4 +131,55 @@ public ECPublicKey loadPublicKey(String pemKeyText) {
122131
}
123132
}
124133

134+
/**
135+
* Loads an EC public key from the given hexadecimal x and y coordinates alongside the curve name.
136+
* <p>
137+
* This method converts the hexadecimal string representations of the EC point coordinates into {@link BigInteger}
138+
* instances, then constructs an {@link ECPoint} and retrieves the corresponding {@link ECParameterSpec} for the
139+
* named curve. Subsequently, it utilises the {@link KeyFactory} to generate an {@link ECPublicKey}.
140+
* <p>
141+
* Only curves listed in {@link #SUPPORTED_CURVES} are supported. Should the specified curve name be unsupported,
142+
* or if key construction fails due to invalid parameters or unsupported algorithms, a {@link KeyLoadingException}
143+
* will be thrown.
144+
*
145+
* @param xHex the hexadecimal string representing the x-coordinate of the EC point
146+
* @param yHex the hexadecimal string representing the y-coordinate of the EC point
147+
* @param curveName the name of the elliptic curve
148+
* @return the {@link ECPublicKey} generated from the specified coordinates and curve
149+
* @throws KeyLoadingException if the curve is unsupported or key generation fails
150+
*/
151+
@Override
152+
public ECPublicKey loadPublicKey(String xHex, String yHex, String curveName) {
153+
if (!SUPPORTED_CURVES.contains(curveName)) {
154+
throw new KeyLoadingException("Given curve is not supported yet.");
155+
}
156+
157+
try {
158+
// Convert hex string coordinates to BigInteger
159+
var x = new BigInteger(xHex, 16);
160+
var y = new BigInteger(yHex, 16);
161+
162+
// Create ECPoint with (x, y)
163+
var ecPoint = new ECPoint(x, y);
164+
165+
// Get EC parameter spec for the named curve
166+
var parameters = AlgorithmParameters.getInstance("EC");
167+
parameters.init(new ECGenParameterSpec(curveName));
168+
var ecParameterSpec = parameters.getParameterSpec(ECParameterSpec.class);
169+
170+
// Create ECPublicKeySpec with point and curve params
171+
var pubSpec = new ECPublicKeySpec(ecPoint, ecParameterSpec);
172+
173+
// Generate public key using KeyFactory
174+
var publicKey = keyFactory.generatePublic(pubSpec);
175+
176+
if (publicKey instanceof ECPublicKey ecPublicKey) {
177+
return ecPublicKey;
178+
} else {
179+
throw new KeyLoadingException("Cannot load EC public key with given x, y and curve name.");
180+
}
181+
} catch (NoSuchAlgorithmException | InvalidParameterSpecException | InvalidKeySpecException e) {
182+
throw new KeyLoadingException("Cannot load EC public key with given x, y and curve name.", e);
183+
}
184+
}
125185
}

key-pair-loader/src/main/java/com/onixbyte/security/impl/RsaKeyLoader.java renamed to key-pair-loader/src/main/java/com/onixbyte/security/impl/RSAKeyLoader.java

Lines changed: 35 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -20,13 +20,12 @@
2020
import com.onixbyte.security.KeyLoader;
2121
import com.onixbyte.security.exception.KeyLoadingException;
2222

23+
import java.math.BigInteger;
2324
import java.security.KeyFactory;
2425
import java.security.NoSuchAlgorithmException;
2526
import java.security.interfaces.RSAPrivateKey;
2627
import java.security.interfaces.RSAPublicKey;
27-
import java.security.spec.InvalidKeySpecException;
28-
import java.security.spec.PKCS8EncodedKeySpec;
29-
import java.security.spec.X509EncodedKeySpec;
28+
import java.security.spec.*;
3029
import java.util.Base64;
3130

3231
/**
@@ -44,9 +43,12 @@
4443
* @see KeyLoader
4544
* @see KeyLoadingException
4645
*/
47-
public class RsaKeyLoader implements KeyLoader {
46+
public class RSAKeyLoader implements KeyLoader {
4847

4948
private final Base64.Decoder decoder;
49+
50+
private final Base64.Decoder urlDecoder;
51+
5052
private final KeyFactory keyFactory;
5153

5254
/**
@@ -55,9 +57,10 @@ public class RsaKeyLoader implements KeyLoader {
5557
* This constructor initialises the Base64 decoder and the RSA {@link KeyFactory}. It may throw
5658
* a {@link KeyLoadingException} if the RSA algorithm is not available.
5759
*/
58-
public RsaKeyLoader() {
60+
public RSAKeyLoader() {
5961
try {
6062
this.decoder = Base64.getDecoder();
63+
this.urlDecoder = Base64.getUrlDecoder();
6164
this.keyFactory = KeyFactory.getInstance("RSA");
6265
} catch (NoSuchAlgorithmException e) {
6366
throw new KeyLoadingException(e);
@@ -133,4 +136,31 @@ public RSAPublicKey loadPublicKey(String pemKeyText) {
133136
throw new KeyLoadingException("Key spec is invalid.", e);
134137
}
135138
}
139+
140+
/**
141+
* Get the public key with given modulus and public exponent.
142+
*
143+
* @param modulus the modulus
144+
* @param exponent the public exponent
145+
* @return generated public key object from the provided key specification
146+
* @see KeyFactory#getInstance(String)
147+
* @see KeyFactory#generatePublic(KeySpec)
148+
*/
149+
@Override
150+
public RSAPublicKey loadPublicKey(String modulus, String exponent) {
151+
try {
152+
var _modulus = new BigInteger(1, urlDecoder.decode(modulus));
153+
var _exponent = new BigInteger(1, urlDecoder.decode(exponent));
154+
155+
var keySpec = new RSAPublicKeySpec(_modulus, _exponent);
156+
var kf = KeyFactory.getInstance("RSA");
157+
if (kf.generatePublic(keySpec) instanceof RSAPublicKey rsaPublicKey) {
158+
return rsaPublicKey;
159+
} else {
160+
throw new KeyLoadingException("Cannot generate RSA public key with given modulus and exponent.");
161+
}
162+
} catch (NoSuchAlgorithmException | InvalidKeySpecException e) {
163+
throw new KeyLoadingException("Cannot generate RSA public key with given modulus and exponent.", e);
164+
}
165+
}
136166
}

simple-jwt-authzero/src/main/java/com/onixbyte/simplejwt/authzero/AuthzeroTokenResolver.java

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -19,8 +19,7 @@
1919

2020
import com.onixbyte.devkit.utils.Base64Util;
2121
import com.onixbyte.guid.GuidCreator;
22-
import com.onixbyte.security.KeyLoader;
23-
import com.onixbyte.security.impl.EcKeyLoader;
22+
import com.onixbyte.security.impl.ECKeyLoader;
2423
import com.onixbyte.simplejwt.TokenPayload;
2524
import com.onixbyte.simplejwt.TokenResolver;
2625
import com.onixbyte.simplejwt.annotations.ExcludeFromPayload;
@@ -43,7 +42,6 @@
4342
import org.slf4j.LoggerFactory;
4443

4544
import java.lang.reflect.InvocationTargetException;
46-
import java.security.NoSuchAlgorithmException;
4745
import java.security.interfaces.ECPrivateKey;
4846
import java.security.interfaces.ECPublicKey;
4947
import java.time.Duration;
@@ -179,7 +177,7 @@ public Builder secret(String secret) {
179177
* @return the builder instance
180178
*/
181179
public Builder keyPair(String publicKey, String privateKey) {
182-
var keyLoader = new EcKeyLoader();
180+
var keyLoader = new ECKeyLoader();
183181
this.publicKey = keyLoader.loadPublicKey(publicKey);
184182
this.privateKey = keyLoader.loadPrivateKey(privateKey);
185183
return this;

0 commit comments

Comments
 (0)