diff --git a/build.gradle.kts b/build.gradle.kts index fb67a8f..ed34796 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -51,12 +51,6 @@ subprojects { testAnnotationProcessor("org.projectlombok:lombok:$lombokVersion") } - repositories { - mavenLocal() - maven(url = "https://maven.proxy.ustclug.org/maven2/") - mavenCentral() - } - tasks.withType { options.encoding = "UTF-8" } diff --git a/devkit-utils/src/main/java/com/onixbyte/devkit/utils/AesUtil.java b/devkit-utils/src/main/java/com/onixbyte/devkit/utils/AesUtil.java index bc9ec22..14baad1 100644 --- a/devkit-utils/src/main/java/com/onixbyte/devkit/utils/AesUtil.java +++ b/devkit-utils/src/main/java/com/onixbyte/devkit/utils/AesUtil.java @@ -34,8 +34,7 @@ import java.util.UUID; /** - * {@link AesUtil} can help you encrypt and decrypt data with specified secret - * by AES algorithm. + * {@link AesUtil} can help you encrypt and decrypt data with specified secret by AES algorithm. * * @author hubin@baomidou * @version 1.1.0 @@ -81,8 +80,9 @@ public static byte[] decrypt(byte[] data, byte[] secret) { var cipher = Cipher.getInstance(AES_CBC_CIPHER); cipher.init(Cipher.DECRYPT_MODE, secretKeySpec, new IvParameterSpec(secret)); return cipher.doFinal(data); - } catch (NoSuchAlgorithmException | NoSuchPaddingException | UnsupportedOperationException | - InvalidKeyException | InvalidAlgorithmParameterException | IllegalBlockSizeException | + } catch (NoSuchAlgorithmException | NoSuchPaddingException | + UnsupportedOperationException | InvalidKeyException | + InvalidAlgorithmParameterException | IllegalBlockSizeException | BadPaddingException exception) { log.error(exception.getMessage()); for (var stackTraceElement : exception.getStackTrace()) { @@ -100,7 +100,8 @@ public static byte[] decrypt(byte[] data, byte[] secret) { * @return the encryption result or {@code null} if encryption failed */ public static String encrypt(String data, String secret) { - return Base64.getEncoder().encodeToString(encrypt(data.getBytes(StandardCharsets.UTF_8), secret.getBytes(StandardCharsets.UTF_8))); + return Base64.getEncoder().encodeToString(encrypt(data.getBytes(StandardCharsets.UTF_8), + secret.getBytes(StandardCharsets.UTF_8))); } /** diff --git a/devkit-utils/src/main/java/com/onixbyte/devkit/utils/Base64Util.java b/devkit-utils/src/main/java/com/onixbyte/devkit/utils/Base64Util.java index 41a0cc3..18802bf 100644 --- a/devkit-utils/src/main/java/com/onixbyte/devkit/utils/Base64Util.java +++ b/devkit-utils/src/main/java/com/onixbyte/devkit/utils/Base64Util.java @@ -49,7 +49,7 @@ * provided. It is recommended to specify the charset explicitly to ensure consistent * encoding and decoding. * - * @author Zihlu Wang + * @author zihluwang * @version 1.1.0 * @since 1.0.0 */ diff --git a/devkit-utils/src/main/java/com/onixbyte/devkit/utils/ChainedCalcUtil.java b/devkit-utils/src/main/java/com/onixbyte/devkit/utils/ChainedCalcUtil.java index a8f395e..2ee3092 100644 --- a/devkit-utils/src/main/java/com/onixbyte/devkit/utils/ChainedCalcUtil.java +++ b/devkit-utils/src/main/java/com/onixbyte/devkit/utils/ChainedCalcUtil.java @@ -293,7 +293,9 @@ private ChainedCalcUtil operator(BiFunction * or null if not applicable * @return a ChainedCalcUtil instance with the updated value */ - private ChainedCalcUtil operator(BiFunction operator, Object other, Integer beforeOperateScale) { + private ChainedCalcUtil operator(BiFunction operator, + Object other, + Integer beforeOperateScale) { return baseOperator((otherValue) -> operator.apply(this.value, otherValue), other, @@ -311,7 +313,8 @@ private ChainedCalcUtil operator(BiFunction * @return a ChainedCalcUtil instance with the updated value */ private synchronized ChainedCalcUtil baseOperator(Function operatorFunction, - Object anotherValue, Integer beforeOperateScale) { + Object anotherValue, + Integer beforeOperateScale) { if (Objects.isNull(anotherValue)) { return this; } diff --git a/devkit-utils/src/main/java/com/onixbyte/devkit/utils/HashUtil.java b/devkit-utils/src/main/java/com/onixbyte/devkit/utils/HashUtil.java index f0e4c59..2c24283 100644 --- a/devkit-utils/src/main/java/com/onixbyte/devkit/utils/HashUtil.java +++ b/devkit-utils/src/main/java/com/onixbyte/devkit/utils/HashUtil.java @@ -61,7 +61,7 @@ * for data integrity checks and password storage, but they should not be used for * encryption purposes. * - * @author Zihlu Wang + * @author zihluwang * @version 1.1.0 * @see java.security.MessageDigest * @since 1.0.0 diff --git a/devkit-utils/src/main/java/com/onixbyte/devkit/utils/MapUtil.java b/devkit-utils/src/main/java/com/onixbyte/devkit/utils/MapUtil.java index aa23699..195020c 100644 --- a/devkit-utils/src/main/java/com/onixbyte/devkit/utils/MapUtil.java +++ b/devkit-utils/src/main/java/com/onixbyte/devkit/utils/MapUtil.java @@ -30,9 +30,8 @@ * Note: Since version 1.4.2, this util class removed reflection API and transferred to a safer API. * Please see documentation for more information. * - * @author Zihlu Wang + * @author zihluwang * @version 1.4.2 - * @see com.onixbyte.devkit.utils.unsafe.ReflectMapUtil * @since 1.0.0 */ @Slf4j diff --git a/gradle.properties b/gradle.properties index 7812492..c4abfd9 100644 --- a/gradle.properties +++ b/gradle.properties @@ -15,9 +15,9 @@ # limitations under the License. # -jacksonVersion=2.17.0 +jacksonVersion=2.17.2 javaJwtVersion=4.4.0 -jjwtVersion=0.12.5 +jjwtVersion=0.12.6 junitVersion=5.10.2 logbackVersion=1.5.4 lombokVersion=1.18.30 @@ -26,7 +26,7 @@ springVersion=6.1.3 springBootVersion=3.2.3 buildGroupId=com.onixbyte -buildVersion=1.5.0 +buildVersion=1.6.0 projectUrl=https://onixbyte.com/JDevKit projectGithubUrl=https://github.com/OnixByte/JDevKit licenseName=The Apache License, Version 2.0 diff --git a/simple-jwt-jjwt/build.gradle.kts b/key-pair-loader/build.gradle.kts similarity index 76% rename from simple-jwt-jjwt/build.gradle.kts rename to key-pair-loader/build.gradle.kts index 30d6484..17c4fd3 100644 --- a/simple-jwt-jjwt/build.gradle.kts +++ b/key-pair-loader/build.gradle.kts @@ -24,20 +24,11 @@ val projectGithubUrl: String by project val licenseName: String by project val licenseUrl: String by project -val jacksonVersion: String by project -val jjwtVersion: String by project - group = buildGroupId version = buildVersion dependencies { - implementation(project(":devkit-utils")) - implementation(project(":guid")) - implementation("com.fasterxml.jackson.core:jackson-databind:$jacksonVersion") - implementation("io.jsonwebtoken:jjwt-api:$jjwtVersion") - implementation("io.jsonwebtoken:jjwt-impl:$jjwtVersion") - implementation("io.jsonwebtoken:jjwt-jackson:$jjwtVersion") - implementation(project(":simple-jwt-facade")) + implementation(project(":devkit-core")) } java { @@ -53,14 +44,15 @@ tasks.test { publishing { publications { - create("simpleJwtJjwt") { + create("keyPairLoader") { groupId = buildGroupId - artifactId = "simple-jwt-jjwt" + artifactId = "key-pair-loader" version = buildVersion pom { - name = "Simple JWT :: JJWT" - description = "SSimple JWT implemented with io.jsonwebtoken:jjwt." + name = "Key Pair Loader" + description = + "This module can easily load key pairs from a PEM content." url = projectUrl licenses { @@ -89,7 +81,7 @@ publishing { from(components["java"]) signing { - sign(publishing.publications["simpleJwtJjwt"]) + sign(publishing.publications["keyPairLoader"]) } } 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 new file mode 100644 index 0000000..4a4b3e1 --- /dev/null +++ b/key-pair-loader/src/main/java/com/onixbyte/security/KeyLoader.java @@ -0,0 +1,100 @@ +/* + * Copyright (C) 2024-2024 OnixByte. + * + * 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. + */ + +package com.onixbyte.security; + +import com.onixbyte.security.exception.KeyLoadingException; +import lombok.extern.slf4j.Slf4j; + +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.util.Base64; + +/** + * KeyLoader can load key pairs from PEM formated content. + * + * @author zihluwang + * @version 1.6.0 + * @since 1.6.0 + */ +@Slf4j +public class KeyLoader { + + /** + * Private constructor prevents from being initialised. + */ + private KeyLoader() { + } + + /** + * Load ECDSA private key from pem-formatted key text. + * + * @param pemKeyText pem-formatted key text + * @return loaded private key + * @throws KeyLoadingException if the generated key is not a {@link ECPrivateKey} instance, + * or EC Key Factory is not loaded, or key spec is invalid + */ + public static ECPrivateKey loadEcdsaPrivateKey(String pemKeyText) { + try { + var decodedKeyString = Base64.getDecoder().decode(pemKeyText); + var keySpec = new PKCS8EncodedKeySpec(decodedKeyString); + var keyFactory = KeyFactory.getInstance("EC"); + var _key = keyFactory.generatePrivate(keySpec); + if (_key instanceof ECPrivateKey privateKey) { + return privateKey; + } else { + throw new KeyLoadingException("Unable to load private key from pem-formatted key text."); + } + } catch (NoSuchAlgorithmException e) { + throw new KeyLoadingException("Cannot get EC Key Factory.", e); + } catch (InvalidKeySpecException e) { + throw new KeyLoadingException("Key spec is invalid.", e); + } + } + + /** + * Load ECDSA public key from pem-formatted key text. + * + * @param pemKeyText pem-formatted key text + * @return loaded private key + * @throws KeyLoadingException if the generated key is not a {@link ECPrivateKey} instance, + * or EC Key Factory is not loaded, or key spec is invalid + */ + public static ECPublicKey loadEcdsaPublicKey(String pemKeyText) { + try { + var keyBytes = Base64.getDecoder().decode(pemKeyText); + var spec = new X509EncodedKeySpec(keyBytes); + var keyFactory = KeyFactory.getInstance("EC"); + var key = keyFactory.generatePublic(spec); + if (key instanceof ECPublicKey publicKey) { + return publicKey; + } else { + throw new KeyLoadingException("Unable to load private key from pem-formatted key text."); + } + } catch (NoSuchAlgorithmException e) { + throw new KeyLoadingException("Cannot get EC Key Factory.", e); + } catch (InvalidKeySpecException e) { + throw new KeyLoadingException("Key spec is invalid.", e); + } + } + +} diff --git a/key-pair-loader/src/main/java/com/onixbyte/security/exception/KeyLoadingException.java b/key-pair-loader/src/main/java/com/onixbyte/security/exception/KeyLoadingException.java new file mode 100644 index 0000000..2c36b10 --- /dev/null +++ b/key-pair-loader/src/main/java/com/onixbyte/security/exception/KeyLoadingException.java @@ -0,0 +1,79 @@ +/* + * Copyright (C) 2024-2024 OnixByte. + * + * 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. + */ + +package com.onixbyte.security.exception; + +/** + * {@code KeyLoadingException} is an exception indicating an error occurred while loading a key. + * + * @author zihluwang + * @version 1.6.0 + * @since 1.6.0 + */ +public class KeyLoadingException extends RuntimeException { + + /** + * Creates a new instance of {@code KeyLoadingException} without a specific message or cause. + */ + public KeyLoadingException() { + } + + /** + * Creates a new instance of {@code KeyLoadingException} with the specified detail message. + * + * @param message the detail message + */ + public KeyLoadingException(String message) { + super(message); + } + + /** + * Creates a new instance of {@code KeyLoadingException} with the specified detail message + * and cause. + * + * @param message the detail message + * @param cause the cause of this exception + */ + public KeyLoadingException(String message, Throwable cause) { + super(message, cause); + } + + /** + * Creates a new instance of {@code KeyLoadingException} with the specified cause. + * + * @param cause the cause of this exception + */ + public KeyLoadingException(Throwable cause) { + super(cause); + } + + /** + * Constructs a new exception with the specified detail message, cause, suppression enabled + * or disabled, and writable stack trace enabled or disabled. + * + * @param message the detail message + * @param cause the cause of this exception + * @param enableSuppression whether suppression is enabled or disabled + * @param writableStackTrace whether the stack trace should be writable + */ + public KeyLoadingException(String message, + Throwable cause, + boolean enableSuppression, + boolean writableStackTrace) { + super(message, cause, enableSuppression, writableStackTrace); + } +} diff --git a/simple-jwt-jjwt/src/main/resources/logback.xml b/key-pair-loader/src/main/resources/logback.xml similarity index 97% rename from simple-jwt-jjwt/src/main/resources/logback.xml rename to key-pair-loader/src/main/resources/logback.xml index f229a7e..1cf5a50 100644 --- a/simple-jwt-jjwt/src/main/resources/logback.xml +++ b/key-pair-loader/src/main/resources/logback.xml @@ -1,6 +1,6 @@