diff --git a/README.md b/README.md index 3ad658c..8ab0faf 100644 --- a/README.md +++ b/README.md @@ -36,11 +36,7 @@ The folder test-keystore is used for testing and should not be used for any prod ``` curl -i -H "Accept: application/json" -H "Content-Type: application/json" -X POST -d '{"controller":"com.microlib.controller.JwtService" , "action":"getToken"}' http://localhost:9000 -curl -i -H "Accept: application/json" -H "Content-Type: application/json" -X POST -d '{"controller":"com.microlib.controller.JwtService" , "action":"createAndSignToken", "key-id":"test-keystore"}' http://localhost:9000 -curl -i -H "Accept: application/json" -H "Content-Type: application/json" -X POST -d '{"controller":"com.microlib.controller.JwtService" , "action":"verifyToken", "key-id":"test-keystore" , "token": "eyJhbGciOiJSUzI1NiJ9.eyJpc3MiOiJhdXRoMCJ9.gF-JDds389H5l4tk2o7qpuSIzSAEgjfVwTb7c3Tf1InuD7EWk5gjY4kKPP__MGc39HfOobjqUMsUFAJBAJYOJxKmfLMBCLr5TXMMeLcc3-qZw3NZ0DDhq76yLiVA_P3pBm1k-kKtZQvwRY8VrLN9JfBm0BDy3f2wvNRmDXQLHAU33fi4zACpGcTJ9TfNBoY84sOGUBhd73yxPLr4lBhYrFjcqGboZDNzg2LdisTVP1I_9KlHA4d8-H5LHYOcwiFD-hFZteKl52jslKfNucHgrhn0D1iLf4YiE92yNVobLAkVN_qPG8ZX8sNlA5AahIqKenk6hK_C0f1LTGzc6ZxXMA"}' http://localhost:9000 +curl -i -H "Accept: application/json" -H "Content-Type: application/json" -X POST -d '{"controller":"com.microlib.controller.JwtService" , "action":"createAndSignToken", "key-id":"test"}' http://localhost:9000 +curl -i -H "Accept: application/json" -H "Content-Type: application/json" -X POST -d '{"controller":"com.microlib.controller.JwtService" , "action":"verifyToken", "key-id":"test" , "token": "eyJhbGciOiJSUzI1NiJ9.eyJpc3MiOiJhdXRoMCJ9.gF-JDds389H5l4tk2o7qpuSIzSAEgjfVwTb7c3Tf1InuD7EWk5gjY4kKPP__MGc39HfOobjqUMsUFAJBAJYOJxKmfLMBCLr5TXMMeLcc3-qZw3NZ0DDhq76yLiVA_P3pBm1k-kKtZQvwRY8VrLN9JfBm0BDy3f2wvNRmDXQLHAU33fi4zACpGcTJ9TfNBoY84sOGUBhd73yxPLr4lBhYrFjcqGboZDNzg2LdisTVP1I_9KlHA4d8-H5LHYOcwiFD-hFZteKl52jslKfNucHgrhn0D1iLf4YiE92yNVobLAkVN_qPG8ZX8sNlA5AahIqKenk6hK_C0f1LTGzc6ZxXMA"}' http://localhost:9000 -``` - -## Improvements - -Read all key pairs into memory at server startup \ No newline at end of file +``` \ No newline at end of file diff --git a/test-keystore/private_key.der b/app-keystore/demoapp/private_key.der similarity index 100% rename from test-keystore/private_key.der rename to app-keystore/demoapp/private_key.der diff --git a/test-keystore/public_key.der b/app-keystore/demoapp/public_key.der similarity index 100% rename from test-keystore/public_key.der rename to app-keystore/demoapp/public_key.der diff --git a/app-keystore/mongodb/private_key.der b/app-keystore/mongodb/private_key.der new file mode 100644 index 0000000..9c8711e Binary files /dev/null and b/app-keystore/mongodb/private_key.der differ diff --git a/app-keystore/mongodb/public_key.der b/app-keystore/mongodb/public_key.der new file mode 100644 index 0000000..a46417c Binary files /dev/null and b/app-keystore/mongodb/public_key.der differ diff --git a/test-keystore/app.rsa b/app-keystore/test/app.rsa similarity index 100% rename from test-keystore/app.rsa rename to app-keystore/test/app.rsa diff --git a/test-keystore/app.rsa.pub b/app-keystore/test/app.rsa.pub similarity index 100% rename from test-keystore/app.rsa.pub rename to app-keystore/test/app.rsa.pub diff --git a/app-keystore/test/private_key.der b/app-keystore/test/private_key.der new file mode 100644 index 0000000..9c8711e Binary files /dev/null and b/app-keystore/test/private_key.der differ diff --git a/test-keystore/private_key.pem b/app-keystore/test/private_key.pem similarity index 100% rename from test-keystore/private_key.pem rename to app-keystore/test/private_key.pem diff --git a/app-keystore/test/public_key.der b/app-keystore/test/public_key.der new file mode 100644 index 0000000..a46417c Binary files /dev/null and b/app-keystore/test/public_key.der differ diff --git a/app-keystore/usermanager/private_key.der b/app-keystore/usermanager/private_key.der new file mode 100644 index 0000000..9c8711e Binary files /dev/null and b/app-keystore/usermanager/private_key.der differ diff --git a/app-keystore/usermanager/public_key.der b/app-keystore/usermanager/public_key.der new file mode 100644 index 0000000..a46417c Binary files /dev/null and b/app-keystore/usermanager/public_key.der differ diff --git a/make.sh b/make.sh index a4761f6..2ebd407 100755 --- a/make.sh +++ b/make.sh @@ -26,24 +26,24 @@ function usage() { function clean() { rm -rf classes/com/* echo "Task : [clean] completed" - echo " " + echo " " } function compile() { -find src/ -name \*.java -print > file.list + find src/ -name \*.java -print > file.list javac -g -d classes -cp $CP @file.list echo "Task : [compile] completed" cp src/com/microlib/server/*.properties classes/com/microlib/server/ cp src/com/microlib/jndi/service/*.properties classes/com/microlib/jndi/service/ cp src/*.properties classes/ echo "Task : [copying resources] completed" - echo " " + echo " " } function run() { java -cp $CP com.microlib.server.TheServer 9000 100 echo "Task : [run] completed" - echo " " + echo " " } diff --git a/src/com/microlib/controller/JwtService.java b/src/com/microlib/controller/JwtService.java index 24e34f9..91694aa 100644 --- a/src/com/microlib/controller/JwtService.java +++ b/src/com/microlib/controller/JwtService.java @@ -3,16 +3,20 @@ import com.auth0.jwt.*; import com.auth0.jwt.algorithms.*; import com.auth0.jwt.exceptions.*; -import java.nio.file.*; -import java.io.*; import java.security.*; import java.security.spec.*; import java.security.interfaces.*; import org.apache.commons.logging.*; import org.apache.commons.logging.impl.*; -import java.util.Map; import java.util.List; +import java.util.Map; +import java.util.Hashtable; +import javax.naming.Context; +import javax.naming.InitialContext; import com.microlib.dataformat.*; +import com.microlib.jndi.service.*; +import java.io.UnsupportedEncodingException; +import javax.naming.NamingException; public class JwtService implements ExecInterface { @@ -20,6 +24,9 @@ public class JwtService implements ExecInterface { private boolean bRunning = false; private String name; private static org.apache.commons.logging.Log log; + final static String jndiName = "java/KeyStore"; + private KeyPairStoreImpl keyStore; + public boolean isRunning() { return bRunning; @@ -56,36 +63,48 @@ public String doProcess(Map map) { // create a token with our generated private rsa key // use the key-id to retrieve the correct kea if (null == map.get("key-id")) { + log.error("no key-id found "); response = json.message("ERROR no key-id found ", "KO"); } else { - byte[] keyBytes = Files.readAllBytes(new File(map.get("key-id").toString() + "/private_key.der").toPath()); - PKCS8EncodedKeySpec spec = new PKCS8EncodedKeySpec(keyBytes); - KeyFactory kf = KeyFactory.getInstance("RSA"); - RSAPrivateKey key = (RSAPrivateKey) kf.generatePrivate(spec); - String token = JWT.create().withIssuer("auth0").sign(Algorithm.RSA256(key)); - log.info("Signed Token : " + token); - response = json.message("Signed Token " + token, "OK"); + byte[] keyBytes = keyStore.getPrivateByteArray(map.get("key-id").toString()); + if (null == keyBytes) { + log.error("key-id " + map.get("key-id").toString() + " not found"); + response = json.message("ERROR key-id " + map.get("key-id").toString() + " not found ", "KO"); + } else { + PKCS8EncodedKeySpec spec = new PKCS8EncodedKeySpec(keyBytes); + KeyFactory kf = KeyFactory.getInstance("RSA"); + RSAPrivateKey key = (RSAPrivateKey) kf.generatePrivate(spec); + String token = JWT.create().withIssuer("auth0").sign(Algorithm.RSA256(key)); + log.info("Signed Token : " + token); + response = json.message("Signed Token " + token, "OK"); + } } - } catch (JWTCreationException | IOException | NoSuchAlgorithmException | InvalidKeySpecException exception) { + } catch (JWTCreationException | NoSuchAlgorithmException | InvalidKeySpecException exception) { log.error(exception); response = json.message("ERROR " + exception.toString(), "KO"); } } else if (map.get("action").toString().equals("verifyToken")) { try { if (null == map.get("key-id")) { + log.error("no key-id found "); response = json.message("ERROR no key-id found ", "KO"); } else { - byte[] keyBytes = Files.readAllBytes(new File(map.get("key-id").toString() + "/public_key.der").toPath()); - X509EncodedKeySpec spec = new X509EncodedKeySpec(keyBytes); - KeyFactory kf = KeyFactory.getInstance("RSA"); - RSAPublicKey key = (RSAPublicKey) kf.generatePublic(spec); - String token = map.get("token").toString(); - JWTVerifier verifier = JWT.require(Algorithm.RSA256(key)).withIssuer("auth0").build(); - JWT jwt = (JWT) verifier.verify(token); - log.info("Token verified " + jwt); - response = json.message("Token verified ", "OK"); + byte[] keyBytes = keyStore.getPublicByteArray(map.get("key-id").toString()); + if (null == keyBytes) { + log.error("key-id " + map.get("key-id").toString() + " not found"); + response = json.message("ERROR key-id " + map.get("key-id").toString() + " not found ", "KO"); + } else { + X509EncodedKeySpec spec = new X509EncodedKeySpec(keyBytes); + KeyFactory kf = KeyFactory.getInstance("RSA"); + RSAPublicKey key = (RSAPublicKey) kf.generatePublic(spec); + String token = map.get("token").toString(); + JWTVerifier verifier = JWT.require(Algorithm.RSA256(key)).withIssuer("auth0").build(); + JWT jwt = (JWT) verifier.verify(token); + log.info("Token verified " + jwt); + response = json.message("Token verified ", "OK"); + } } - } catch (JWTVerificationException | JWTCreationException | IOException | NoSuchAlgorithmException + } catch (JWTVerificationException | JWTCreationException | NoSuchAlgorithmException | InvalidKeySpecException exception) { log.error(exception); response = json.message("ERROR " + exception.toString(), "KO"); @@ -97,6 +116,14 @@ public String doProcess(Map map) { } public void init(String sIn) { - log = LogFactory.getLog(JwtService.class); + try { + log = LogFactory.getLog(JwtService.class); + Hashtable ht = new Hashtable(); + ht.put("java.naming.factory.initial", "com.microlib.jndi.DSInitCtxFactory"); + Context ctx = new InitialContext(ht); + keyStore = (KeyPairStoreImpl) ctx.lookup(jndiName); + } catch(NamingException e) { + // we can't assume that the log is available + } } } \ No newline at end of file diff --git a/src/com/microlib/jndi/service/JndiInterface.java b/src/com/microlib/jndi/service/JndiInterface.java index 208dc64..3e0b3b0 100644 --- a/src/com/microlib/jndi/service/JndiInterface.java +++ b/src/com/microlib/jndi/service/JndiInterface.java @@ -8,7 +8,7 @@ * @author: Luigi Mario Zuccarelli * @version: 1.10 * @date: Generated on Mon Dec 29 15:30:20 CEST 2014 - * @file: PluginInterface.java + * @file: JndiInterface.java * */ diff --git a/src/com/microlib/jndi/service/KeyPairStoreImpl.java b/src/com/microlib/jndi/service/KeyPairStoreImpl.java new file mode 100644 index 0000000..22a5c0e --- /dev/null +++ b/src/com/microlib/jndi/service/KeyPairStoreImpl.java @@ -0,0 +1,74 @@ +package com.microlib.jndi.service; + + +import org.apache.commons.logging.*; +import org.apache.commons.logging.impl.*; +import java.util.*; +import java.nio.file.*; +import java.io.*; + +/** + * @(#) KeyPairStoreImpl + * + * In the light of open source software you are free to do what you like with this code. + * Redistribution and use in source and binary forms, with or without + * modification, is absolutely permitted - just keep the credits please. + * + * @author: Luigi Mario Zuccarelli + * @version: 1.10 + * @date: Generated on Mon Dec 29 15:30:20 CEST 2014 + * @file: KeyPairStoreImpl.java + * + * + * + * Tags for CVS + * $Author$ + * $Id$ + * $Date$ + * + */ + +public class KeyPairStoreImpl implements JndiInterface { + + private static org.apache.commons.logging.Log log; + private Map publicKeyStore = new HashMap(); + private Map privateKeyStore = new HashMap(); + + public KeyPairStoreImpl() { + log = LogFactory.getLog(KeyPairStoreImpl.class); + } + + public void init(String sIn) { + + try { + File[] directories = new File("app-keystore").listFiles(File::isDirectory); + for (File f : directories) { + log.info("Loading keypair for " + f.getName()); + privateKeyStore.put(f.getName(), Files.readAllBytes(new File(f.getPath() + "/private_key.der").toPath())); + publicKeyStore.put(f.getName(), Files.readAllBytes(new File(f.getPath() + "/public_key.der").toPath())); + } + } catch (IOException io) { + log.error(io); + } + } + + public byte[] getPrivateByteArray(String sIn) { + return privateKeyStore.get(sIn); + } + + public byte[] getPublicByteArray(String sIn) { + return publicKeyStore.get(sIn); + } + + public void destroy() { + try { + log.info(" "); + privateKeyStore = null; + publicKeyStore = null; + } catch (Exception e) { + log.error(e); + } finally { + log = null; + } + } +} diff --git a/src/com/microlib/server/TheServer.java b/src/com/microlib/server/TheServer.java index ea91952..eefa9a2 100644 --- a/src/com/microlib/server/TheServer.java +++ b/src/com/microlib/server/TheServer.java @@ -93,6 +93,7 @@ public TheServer(int port, int threads) { log.info(" "); log.info("Press CTRL-C to gracefully shutdown the server "); log.info(" "); + log.info(" "); while (!bStop) { Socket socket = listener.accept(); @@ -128,5 +129,4 @@ public void run() { } } } - } diff --git a/src/com/microlib/server/TheServer.properties b/src/com/microlib/server/TheServer.properties index 10a4fed..b7cc835 100755 --- a/src/com/microlib/server/TheServer.properties +++ b/src/com/microlib/server/TheServer.properties @@ -4,7 +4,7 @@ # plugins [service list] -jndiplugin.count=11 +jndiplugin.count=12 # BasicDataSourceImpl name.0=com.microlib.jndi.service.BasicDataSourceImpl @@ -71,3 +71,8 @@ use.10=true value.10=0 jndi.10=java/Scheduler +# KeyPairStoreImpl +name.11=com.microlib.jndi.service.KeyPairStoreImpl +use.11=true +value.11=0 +jndi.11=java/KeyStore