From 22e0787ec31df8208bc9639d23cfd35a5079cfc6 Mon Sep 17 00:00:00 2001 From: Kshitij09 Date: Tue, 9 Nov 2021 03:38:33 +0530 Subject: [PATCH] Bugfix: handle secret keys correctly (#1) Current key providers were expecting key files to be stored in the Plain Text format. Following the Key Generation Guide, we've updated the Key Initialization to make use of binary (.DER) format for Private Key and (X.509 Certificate) template for Public Key Signed-off-by: Kshitij Patil --- springboot-app/build.gradle | 2 +- .../tazabazar/configuration/JwtConfig.java | 2 + .../tazabazar/security/JwtCreateService.java | 2 +- .../security/JwtValidateService.java | 4 +- .../jwt/JwtInitializationException.java | 2 +- .../security/jwt/JwtPrivateKeyProvider.java | 40 +++++++---------- .../security/jwt/JwtPublicKeyProvider.java | 43 ++++++++----------- .../tazabazar/utils/ResourceUtil.java | 10 +++++ .../src/main/resources/application.properties | 2 + 9 files changed, 51 insertions(+), 56 deletions(-) diff --git a/springboot-app/build.gradle b/springboot-app/build.gradle index c9ce8df..621859f 100644 --- a/springboot-app/build.gradle +++ b/springboot-app/build.gradle @@ -5,7 +5,7 @@ plugins { } group = 'com.kshitijpatil.tazabazar' -version = '1.2.0' +version = '1.3.0' sourceCompatibility = '11' ext { diff --git a/springboot-app/src/main/java/com/kshitijpatil/tazabazar/configuration/JwtConfig.java b/springboot-app/src/main/java/com/kshitijpatil/tazabazar/configuration/JwtConfig.java index 2a22490..b927a57 100644 --- a/springboot-app/src/main/java/com/kshitijpatil/tazabazar/configuration/JwtConfig.java +++ b/springboot-app/src/main/java/com/kshitijpatil/tazabazar/configuration/JwtConfig.java @@ -9,4 +9,6 @@ @ConfigurationProperties(prefix = "jwt") public class JwtConfig { private Long expirationInMinutes; + private String privateKeyFilepath; + private String publicKeyFilepath; } \ No newline at end of file diff --git a/springboot-app/src/main/java/com/kshitijpatil/tazabazar/security/JwtCreateService.java b/springboot-app/src/main/java/com/kshitijpatil/tazabazar/security/JwtCreateService.java index e38a140..aebf48c 100644 --- a/springboot-app/src/main/java/com/kshitijpatil/tazabazar/security/JwtCreateService.java +++ b/springboot-app/src/main/java/com/kshitijpatil/tazabazar/security/JwtCreateService.java @@ -37,7 +37,7 @@ public String generateToken(String username, List roles) { .setExpiration(dateUtil.toDate(expiryDate)) .setIssuer(jwtIssuer) .setId(UUID.randomUUID().toString()) - .signWith(SignatureAlgorithm.RS256, jwtPrivateKeyProvider.getPrivateKey()) + .signWith(SignatureAlgorithm.RS256, jwtPrivateKeyProvider.get()) .claim(CLAIM_USERNAME, username) .claim(CLAIM_ROLES, roles.toArray()) .compact(); diff --git a/springboot-app/src/main/java/com/kshitijpatil/tazabazar/security/JwtValidateService.java b/springboot-app/src/main/java/com/kshitijpatil/tazabazar/security/JwtValidateService.java index 6b9110a..3082629 100644 --- a/springboot-app/src/main/java/com/kshitijpatil/tazabazar/security/JwtValidateService.java +++ b/springboot-app/src/main/java/com/kshitijpatil/tazabazar/security/JwtValidateService.java @@ -16,7 +16,7 @@ public class JwtValidateService { public boolean validateToken(String token) { try { Jwts.parser() - .setSigningKey(jwtPublicKeyProvider.getPublicKey()) + .setSigningKey(jwtPublicKeyProvider.get()) .parseClaimsJws(token); return true; } catch (SignatureException ex) { @@ -35,7 +35,7 @@ public boolean validateToken(String token) { public Claims getClaims(String token) { return Jwts.parser() - .setSigningKey(jwtPublicKeyProvider.getPublicKey()) + .setSigningKey(jwtPublicKeyProvider.get()) .parseClaimsJws(token) .getBody(); } diff --git a/springboot-app/src/main/java/com/kshitijpatil/tazabazar/security/jwt/JwtInitializationException.java b/springboot-app/src/main/java/com/kshitijpatil/tazabazar/security/jwt/JwtInitializationException.java index 0fc09d9..53afbd8 100644 --- a/springboot-app/src/main/java/com/kshitijpatil/tazabazar/security/jwt/JwtInitializationException.java +++ b/springboot-app/src/main/java/com/kshitijpatil/tazabazar/security/jwt/JwtInitializationException.java @@ -2,6 +2,6 @@ public class JwtInitializationException extends RuntimeException { public JwtInitializationException(Throwable e) { - super("Something went wong while reading private key!", e); + super("Error initializing public/private key!", e); } } \ No newline at end of file diff --git a/springboot-app/src/main/java/com/kshitijpatil/tazabazar/security/jwt/JwtPrivateKeyProvider.java b/springboot-app/src/main/java/com/kshitijpatil/tazabazar/security/jwt/JwtPrivateKeyProvider.java index e1f5d30..0428c50 100644 --- a/springboot-app/src/main/java/com/kshitijpatil/tazabazar/security/jwt/JwtPrivateKeyProvider.java +++ b/springboot-app/src/main/java/com/kshitijpatil/tazabazar/security/jwt/JwtPrivateKeyProvider.java @@ -1,48 +1,38 @@ package com.kshitijpatil.tazabazar.security.jwt; -import com.kshitijpatil.tazabazar.utils.Base64Util; -import com.kshitijpatil.tazabazar.utils.ReadKeyMixin; +import com.kshitijpatil.tazabazar.configuration.JwtConfig; import com.kshitijpatil.tazabazar.utils.ResourceUtil; -import lombok.Getter; import lombok.RequiredArgsConstructor; import org.springframework.stereotype.Component; import javax.annotation.PostConstruct; +import java.io.IOException; import java.security.KeyFactory; +import java.security.NoSuchAlgorithmException; import java.security.PrivateKey; -import java.security.spec.EncodedKeySpec; import java.security.spec.InvalidKeySpecException; import java.security.spec.PKCS8EncodedKeySpec; @Component @RequiredArgsConstructor -public class JwtPrivateKeyProvider implements ReadKeyMixin { - @Getter +public class JwtPrivateKeyProvider { private final ResourceUtil resourceUtil; - private final Base64Util base64Util; - - @Getter + private final JwtConfig jwtConfig; private PrivateKey privateKey; @PostConstruct public void init() { - privateKey = readKey( - "classpath:keys/tzb_key.pkcs8.private", - "PRIVATE", - this::privateKeySpec, - this::privateKeyGenerator - ); - } - - private EncodedKeySpec privateKeySpec(String data) { - return new PKCS8EncodedKeySpec(base64Util.decode(data)); - } - - private PrivateKey privateKeyGenerator(KeyFactory kf, EncodedKeySpec spec) { try { - return kf.generatePrivate(spec); - } catch (InvalidKeySpecException e) { - throw new JwtInitializationException(e); + var keyBytes = resourceUtil.readAllBytes(jwtConfig.getPrivateKeyFilepath()); + PKCS8EncodedKeySpec spec = new PKCS8EncodedKeySpec(keyBytes); + KeyFactory keyFactory = KeyFactory.getInstance("RSA"); + privateKey = keyFactory.generatePrivate(spec); + } catch (IOException | NoSuchAlgorithmException | InvalidKeySpecException ex) { + throw new JwtInitializationException(ex); } } + + public PrivateKey get() { + return privateKey; + } } diff --git a/springboot-app/src/main/java/com/kshitijpatil/tazabazar/security/jwt/JwtPublicKeyProvider.java b/springboot-app/src/main/java/com/kshitijpatil/tazabazar/security/jwt/JwtPublicKeyProvider.java index 4b765e8..c838f71 100644 --- a/springboot-app/src/main/java/com/kshitijpatil/tazabazar/security/jwt/JwtPublicKeyProvider.java +++ b/springboot-app/src/main/java/com/kshitijpatil/tazabazar/security/jwt/JwtPublicKeyProvider.java @@ -1,48 +1,39 @@ package com.kshitijpatil.tazabazar.security.jwt; -import com.kshitijpatil.tazabazar.utils.Base64Util; -import com.kshitijpatil.tazabazar.utils.ReadKeyMixin; +import com.kshitijpatil.tazabazar.configuration.JwtConfig; import com.kshitijpatil.tazabazar.utils.ResourceUtil; import lombok.Getter; import lombok.RequiredArgsConstructor; import org.springframework.stereotype.Component; import javax.annotation.PostConstruct; -import java.security.KeyFactory; +import java.io.IOException; import java.security.PublicKey; -import java.security.spec.EncodedKeySpec; -import java.security.spec.InvalidKeySpecException; -import java.security.spec.X509EncodedKeySpec; +import java.security.cert.CertificateException; +import java.security.cert.CertificateFactory; +import java.security.cert.X509Certificate; @Component @RequiredArgsConstructor -public class JwtPublicKeyProvider implements ReadKeyMixin { +public class JwtPublicKeyProvider { @Getter private final ResourceUtil resourceUtil; - private final Base64Util base64Util; - - @Getter + private final JwtConfig jwtConfig; private PublicKey publicKey; @PostConstruct public void init() { - publicKey = readKey( - "classpath:keys/tzb_key.x509.public", - "PUBLIC", - this::publicKeySpec, - this::publicKeyGenerator - ); - } - - private EncodedKeySpec publicKeySpec(String data) { - return new X509EncodedKeySpec(base64Util.decode(data)); - } - - private PublicKey publicKeyGenerator(KeyFactory kf, EncodedKeySpec spec) { try { - return kf.generatePublic(spec); - } catch (InvalidKeySpecException e) { - throw new JwtInitializationException(e); + CertificateFactory certFactory = CertificateFactory.getInstance("X.509"); + var keyInputStream = resourceUtil.getInputStream(jwtConfig.getPublicKeyFilepath()); + X509Certificate cert = (X509Certificate) certFactory.generateCertificate(keyInputStream); + publicKey = cert.getPublicKey(); + } catch (CertificateException | IOException ex) { + throw new JwtInitializationException(ex); } } + + public PublicKey get() { + return publicKey; + } } diff --git a/springboot-app/src/main/java/com/kshitijpatil/tazabazar/utils/ResourceUtil.java b/springboot-app/src/main/java/com/kshitijpatil/tazabazar/utils/ResourceUtil.java index d5dc808..c297128 100644 --- a/springboot-app/src/main/java/com/kshitijpatil/tazabazar/utils/ResourceUtil.java +++ b/springboot-app/src/main/java/com/kshitijpatil/tazabazar/utils/ResourceUtil.java @@ -7,6 +7,7 @@ import org.springframework.util.FileCopyUtils; import java.io.IOException; +import java.io.InputStream; import java.io.InputStreamReader; import java.io.Reader; @@ -24,4 +25,13 @@ public String asString(String resourcePath) throws IOException { return FileCopyUtils.copyToString(reader); } } + + public InputStream getInputStream(String resourcePath) throws IOException { + Resource resource = resourceLoader.getResource(resourcePath); + return resource.getInputStream(); + } + + public byte[] readAllBytes(String resourcePath) throws IOException { + return getInputStream(resourcePath).readAllBytes(); + } } \ No newline at end of file diff --git a/springboot-app/src/main/resources/application.properties b/springboot-app/src/main/resources/application.properties index ba0311b..fa8e3f1 100644 --- a/springboot-app/src/main/resources/application.properties +++ b/springboot-app/src/main/resources/application.properties @@ -5,6 +5,8 @@ springdoc.api-docs.enabled=true springdoc.api-docs.path=/v3/api-docs springdoc.swagger-ui.path=/swagger-ui jwt.expiration-in-minutes=15 +jwt.private-key-filepath=classpath:keys/tzb_key.pkcs8.private +jwt.public-key-filepath=classpath:keys/tzb_key.x509.public spring.datasource.url=jdbc:postgresql://${env.DB_HOST}:5432/${env.DB_NAME} spring.datasource.username=${env.DB_USERNAME} spring.datasource.password=${env.DB_PASSWORD}