22
33import static io .quarkus .smallrye .jwt .deployment .SmallRyeJwtProcessor .MP_JWT_VERIFY_KEY_LOCATION ;
44
5+ import java .io .File ;
6+ import java .io .FileWriter ;
7+ import java .io .IOException ;
8+ import java .nio .file .Path ;
9+ import java .security .GeneralSecurityException ;
510import java .security .Key ;
611import java .security .KeyPair ;
712import java .security .NoSuchAlgorithmException ;
813import java .util .Base64 ;
14+ import java .util .Collection ;
915import java .util .HashMap ;
1016import java .util .Map ;
17+ import java .util .Optional ;
1118import java .util .Set ;
1219import java .util .stream .Collectors ;
1320
1421import org .eclipse .microprofile .config .ConfigProvider ;
1522import org .jboss .logging .Logger ;
1623
24+ import io .quarkus .bootstrap .workspace .ArtifactSources ;
25+ import io .quarkus .bootstrap .workspace .SourceDir ;
1726import io .quarkus .deployment .Feature ;
1827import io .quarkus .deployment .IsNormal ;
1928import io .quarkus .deployment .annotations .BuildProducer ;
2029import io .quarkus .deployment .annotations .BuildStep ;
2130import io .quarkus .deployment .builditem .DevServicesResultBuildItem ;
2231import io .quarkus .deployment .builditem .LiveReloadBuildItem ;
32+ import io .quarkus .deployment .pkg .builditem .CurateOutcomeBuildItem ;
2333import io .smallrye .jwt .util .KeyUtils ;
2434
2535public class SmallryeJwtDevModeProcessor {
2636
2737 private static final Logger LOGGER = Logger .getLogger (SmallryeJwtDevModeProcessor .class );
2838
29- private static final String MP_JWT_VERIFY_PUBLIC_KEY = "mp.jwt.verify.publickey" ;
39+ public static final String MP_JWT_VERIFY_PUBLIC_KEY = "mp.jwt.verify.publickey" ;
3040 private static final String MP_JWT_VERIFY_ISSUER = "mp.jwt.verify.issuer" ;
41+ private static final String SMALLRYE_JWT_DECRYPT_KEY = "smallrye.jwt.decrypt.key" ; // no MP equivalent
3142 private static final String MP_JWT_DECRYPT_KEY_LOCATION = "mp.jwt.decrypt.key.location" ;
3243
3344 private static final String SMALLRYE_JWT_NEW_TOKEN_ISSUER = "smallrye.jwt.new-token.issuer" ;
3445 private static final String SMALLRYE_JWT_SIGN_KEY_LOCATION = "smallrye.jwt.sign.key.location" ;
35- private static final String SMALLRYE_JWT_SIGN_KEY = "smallrye.jwt.sign.key" ;
46+ public static final String SMALLRYE_JWT_SIGN_KEY = "smallrye.jwt.sign.key" ;
47+ private static final String SMALLRYE_JWT_ENCRYPT_KEY = "smallrye.jwt.encrypt.key" ;
3648 private static final String SMALLRYE_JWT_ENCRYPT_KEY_LOCATION = "smallrye.jwt.encrypt.key.location" ;
3749
3850 private static final String NONE = "NONE" ;
@@ -43,10 +55,14 @@ public class SmallryeJwtDevModeProcessor {
4355 private static final Set <String > JWT_SIGN_KEY_PROPERTIES = Set .of (
4456 MP_JWT_VERIFY_KEY_LOCATION ,
4557 MP_JWT_VERIFY_PUBLIC_KEY ,
58+ SMALLRYE_JWT_DECRYPT_KEY ,
4659 MP_JWT_DECRYPT_KEY_LOCATION ,
4760 SMALLRYE_JWT_SIGN_KEY_LOCATION ,
4861 SMALLRYE_JWT_SIGN_KEY ,
62+ SMALLRYE_JWT_ENCRYPT_KEY ,
4963 SMALLRYE_JWT_ENCRYPT_KEY_LOCATION );
64+ public static final String DEV_PRIVATE_KEY_PEM = "dev.privateKey.pem" ;
65+ public static final String DEV_PUBLIC_KEY_PEM = "dev.publicKey.pem" ;
5066
5167 /**
5268 * This build step generates an RSA-256 key pair for development and test modes.
@@ -58,10 +74,15 @@ public class SmallryeJwtDevModeProcessor {
5874 * this build step will add a default issuer, regardless of the above condition.
5975 *
6076 * @throws NoSuchAlgorithmException if RSA-256 key generation fails.
77+ * @throws IOException if persistent key storage fails
6178 */
6279 @ BuildStep (onlyIfNot = { IsNormal .class })
6380 void generateSignKeys (BuildProducer <DevServicesResultBuildItem > devServices ,
64- LiveReloadBuildItem liveReloadBuildItem ) throws NoSuchAlgorithmException {
81+ LiveReloadBuildItem liveReloadBuildItem ,
82+ CurateOutcomeBuildItem curateOutcomeBuildItem ,
83+ Optional <GeneratePersistentDevModeJwtKeysBuildItem > generatePersistentDevModeJwtKeysBuildItem ,
84+ Optional <GenerateEncryptedDevModeJwtKeysBuildItem > generateEncryptedDevModeJwtKeysBuildItem )
85+ throws GeneralSecurityException , IOException {
6586
6687 Set <String > userProps = JWT_SIGN_KEY_PROPERTIES
6788 .stream ()
@@ -71,7 +92,8 @@ void generateSignKeys(BuildProducer<DevServicesResultBuildItem> devServices,
7192 if (!userProps .isEmpty ()) {
7293 // If the user has set the property, we need to avoid adding or overriding it with the
7394 // smallrye default configuration
74- Map <String , String > devServiceProps = addDefaultSmallryePropertiesIfMissing (userProps );
95+ Map <String , String > devServiceProps = addDefaultSmallryePropertiesIfMissing (userProps ,
96+ generateEncryptedDevModeJwtKeysBuildItem );
7597
7698 if (!isConfigPresent (MP_JWT_VERIFY_ISSUER ) && !isConfigPresent (SMALLRYE_JWT_NEW_TOKEN_ISSUER )) {
7799 devServiceProps .put (MP_JWT_VERIFY_ISSUER , DEFAULT_ISSUER );
@@ -88,11 +110,12 @@ void generateSignKeys(BuildProducer<DevServicesResultBuildItem> devServices,
88110 "Please ensure the correct keys/locations are set in production to avoid potential issues." );
89111 if (ctx == null && !liveReloadBuildItem .isLiveReload ()) {
90112 // first execution
91- KeyPair keyPair = KeyUtils . generateKeyPair ( KEY_SIZE );
113+ KeyPair keyPair = generateOrReloadKeyPair ( curateOutcomeBuildItem , generatePersistentDevModeJwtKeysBuildItem );
92114 String publicKey = getStringKey (keyPair .getPublic ());
93115 String privateKey = getStringKey (keyPair .getPrivate ());
94116
95- Map <String , String > devServiceProps = generateDevServiceProperties (publicKey , privateKey );
117+ Map <String , String > devServiceProps = generateDevServiceProperties (publicKey , privateKey ,
118+ generateEncryptedDevModeJwtKeysBuildItem );
96119
97120 if (!isConfigPresent (MP_JWT_VERIFY_ISSUER ) && !isConfigPresent (SMALLRYE_JWT_NEW_TOKEN_ISSUER )) {
98121 devServiceProps .put (MP_JWT_VERIFY_ISSUER , DEFAULT_ISSUER );
@@ -110,7 +133,67 @@ void generateSignKeys(BuildProducer<DevServicesResultBuildItem> devServices,
110133 }
111134 }
112135
113- private Map <String , String > addDefaultSmallryePropertiesIfMissing (Set <String > userConfigs ) {
136+ private KeyPair generateOrReloadKeyPair (CurateOutcomeBuildItem curateOutcomeBuildItem ,
137+ Optional <GeneratePersistentDevModeJwtKeysBuildItem > generatePersistentDevModeJwtKeysBuildItem )
138+ throws GeneralSecurityException , IOException {
139+ if (generatePersistentDevModeJwtKeysBuildItem .isPresent ()) {
140+ File buildDir = getBuildDir (curateOutcomeBuildItem );
141+
142+ buildDir .mkdirs ();
143+ File privateKey = new File (buildDir , DEV_PRIVATE_KEY_PEM );
144+ File publicKey = new File (buildDir , DEV_PUBLIC_KEY_PEM );
145+ if (!privateKey .exists () || !publicKey .exists ()) {
146+ KeyPair keyPair = KeyUtils .generateKeyPair (KEY_SIZE );
147+ LOGGER .infof ("Generating private/public keys for DEV/TEST in %s and %s" , privateKey , publicKey );
148+ try (FileWriter fw = new FileWriter (privateKey )) {
149+ fw .append ("-----BEGIN PRIVATE KEY-----\n " );
150+ fw .append (Base64 .getMimeEncoder ().encodeToString (keyPair .getPrivate ().getEncoded ()));
151+ fw .append ("\n " );
152+ fw .append ("-----END PRIVATE KEY-----\n " );
153+ }
154+ try (FileWriter fw = new FileWriter (publicKey )) {
155+ fw .append ("-----BEGIN PUBLIC KEY-----\n " );
156+ fw .append (Base64 .getMimeEncoder ().encodeToString (keyPair .getPublic ().getEncoded ()));
157+ fw .append ("\n " );
158+ fw .append ("-----END PUBLIC KEY-----\n " );
159+ }
160+ return keyPair ;
161+ } else {
162+ // read from disk
163+ return new KeyPair (KeyUtils .readPublicKey (publicKey .getName ()),
164+ KeyUtils .readPrivateKey (privateKey .getName ()));
165+ }
166+ } else {
167+ return KeyUtils .generateKeyPair (KEY_SIZE );
168+ }
169+ }
170+
171+ public static File getBuildDir (CurateOutcomeBuildItem curateOutcomeBuildItem ) {
172+ File buildDir = null ;
173+ ArtifactSources src = curateOutcomeBuildItem .getApplicationModel ().getAppArtifact ().getSources ();
174+ if (src != null ) { // shouldn't be null in dev mode
175+ Collection <SourceDir > srcDirs = src .getResourceDirs ();
176+ if (srcDirs .isEmpty ()) {
177+ // if the module has no resources dir?
178+ srcDirs = src .getSourceDirs ();
179+ }
180+ if (!srcDirs .isEmpty ()) {
181+ // pick the first resources output dir
182+ Path resourcesOutputDir = srcDirs .iterator ().next ().getOutputDir ();
183+ buildDir = resourcesOutputDir .toFile ();
184+ }
185+ }
186+ if (buildDir == null ) {
187+ // the module doesn't have any sources nor resources, stick to the build dir
188+ buildDir = new File (
189+ curateOutcomeBuildItem .getApplicationModel ().getAppArtifact ().getWorkspaceModule ().getBuildDir (),
190+ "classes" );
191+ }
192+ return buildDir ;
193+ }
194+
195+ private Map <String , String > addDefaultSmallryePropertiesIfMissing (Set <String > userConfigs ,
196+ Optional <GenerateEncryptedDevModeJwtKeysBuildItem > generateEncryptedDevModeJwtKeysBuildItem ) {
114197 HashMap <String , String > devServiceConfigs = new HashMap <>();
115198 if (!userConfigs .contains (SMALLRYE_JWT_SIGN_KEY )) {
116199 devServiceConfigs .put (SMALLRYE_JWT_SIGN_KEY , NONE );
@@ -120,6 +203,16 @@ private Map<String, String> addDefaultSmallryePropertiesIfMissing(Set<String> us
120203 devServiceConfigs .put (MP_JWT_VERIFY_PUBLIC_KEY , NONE );
121204 }
122205
206+ if (generateEncryptedDevModeJwtKeysBuildItem .isPresent ()) {
207+ if (!userConfigs .contains (SMALLRYE_JWT_ENCRYPT_KEY ) && !userConfigs .contains (SMALLRYE_JWT_ENCRYPT_KEY_LOCATION )) {
208+ devServiceConfigs .put (SMALLRYE_JWT_ENCRYPT_KEY , NONE );
209+ }
210+
211+ if (!userConfigs .contains (SMALLRYE_JWT_DECRYPT_KEY ) && !userConfigs .contains (MP_JWT_DECRYPT_KEY_LOCATION )) {
212+ devServiceConfigs .put (SMALLRYE_JWT_DECRYPT_KEY , NONE );
213+ }
214+ }
215+
123216 return devServiceConfigs ;
124217 }
125218
@@ -133,10 +226,15 @@ private DevServicesResultBuildItem smallryeJwtDevServiceWith(Map<String, String>
133226 Feature .SMALLRYE_JWT .name (), null , properties );
134227 }
135228
136- private static Map <String , String > generateDevServiceProperties (String publicKey , String privateKey ) {
229+ private static Map <String , String > generateDevServiceProperties (String publicKey , String privateKey ,
230+ Optional <GenerateEncryptedDevModeJwtKeysBuildItem > generateEncryptedDevModeJwtKeysBuildItem ) {
137231 HashMap <String , String > properties = new HashMap <>();
138232 properties .put (MP_JWT_VERIFY_PUBLIC_KEY , publicKey );
139233 properties .put (SMALLRYE_JWT_SIGN_KEY , privateKey );
234+ if (generateEncryptedDevModeJwtKeysBuildItem .isPresent ()) {
235+ properties .put (SMALLRYE_JWT_ENCRYPT_KEY , publicKey );
236+ properties .put (SMALLRYE_JWT_DECRYPT_KEY , privateKey );
237+ }
140238 return properties ;
141239 }
142240
0 commit comments