Added JWK-set web-controller listing kustvakt public keys.

Change-Id: If8244161d7979008c65e3de5b9154cc5dd427a17
diff --git a/full/src/main/java/de/ids_mannheim/korap/config/FullConfiguration.java b/full/src/main/java/de/ids_mannheim/korap/config/FullConfiguration.java
index 6c215e0..038e402 100644
--- a/full/src/main/java/de/ids_mannheim/korap/config/FullConfiguration.java
+++ b/full/src/main/java/de/ids_mannheim/korap/config/FullConfiguration.java
@@ -1,19 +1,15 @@
 package de.ids_mannheim.korap.config;
 
-import java.io.BufferedReader;
+import java.io.File;
 import java.io.IOException;
 import java.io.InputStream;
-import java.io.InputStreamReader;
 import java.net.MalformedURLException;
 import java.net.URI;
 import java.net.URISyntaxException;
 import java.net.URL;
-import java.security.KeyFactory;
-import java.security.NoSuchAlgorithmException;
+import java.nio.charset.Charset;
 import java.security.interfaces.RSAPrivateKey;
-import java.security.spec.InvalidKeySpecException;
-import java.security.spec.KeySpec;
-import java.security.spec.PKCS8EncodedKeySpec;
+import java.text.ParseException;
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.List;
@@ -22,15 +18,18 @@
 import java.util.regex.Pattern;
 import java.util.stream.Collectors;
 
-import org.apache.commons.codec.binary.Base64;
+import com.nimbusds.jose.JOSEException;
+import com.nimbusds.jose.jwk.JWK;
+import com.nimbusds.jose.jwk.JWKSet;
+import com.nimbusds.jose.jwk.RSAKey;
+import com.nimbusds.jose.util.IOUtils;
 
 import de.ids_mannheim.korap.constant.AuthenticationMethod;
 import de.ids_mannheim.korap.interfaces.EncryptionIface;
 
 /**
  * Configuration for Kustvakt full version including properties
- * concerning
- * authentication and licenses.
+ * concerning authentication and licenses.
  * 
  * @author margaretha
  *
@@ -76,42 +75,15 @@
     private URL issuer;
     private URI issuerURI;
     private RSAPrivateKey rsaPrivateKey;
+    private JWKSet publicKeySet;
+    private String rsaKeyId;
 
-    public FullConfiguration (Properties properties)
-            throws IOException, URISyntaxException, InvalidKeySpecException,
-            NoSuchAlgorithmException {
+    public FullConfiguration (Properties properties) throws Exception {
         super(properties);
-        setRSAPrivateKey();
-    }
-
-    public void setRSAPrivateKey () throws IOException, InvalidKeySpecException,
-            NoSuchAlgorithmException {
-        InputStream is = getClass().getClassLoader()
-                .getResourceAsStream("kustvakt-private.key");
-
-        if (is == null){
-            this.rsaPrivateKey = null;
-            return;
-        }
-            
-        String privateKey = null;
-        try (BufferedReader reader =
-                new BufferedReader(new InputStreamReader(is));) {
-            privateKey = reader.readLine();
-        }
-        byte[] decodedKey = Base64.decodeBase64(privateKey);
-        KeySpec keySpec = new PKCS8EncodedKeySpec(decodedKey);
-        this.rsaPrivateKey = (RSAPrivateKey) KeyFactory.getInstance("RSA")
-                .generatePrivate(keySpec);
-    }
-
-    public RSAPrivateKey getRSAPrivateKey () {
-        return this.rsaPrivateKey;
     }
 
     @Override
-    public void load (Properties properties)
-            throws IOException, URISyntaxException {
+    public void load (Properties properties) throws Exception {
 
         super.load(properties);
         // EM: regex used for storing vc
@@ -128,6 +100,43 @@
 
         setOAuth2Configuration(properties);
         setOpenIdConfiguration(properties);
+        setRSAKeys(properties);
+    }
+
+    private void setRSAKeys (Properties properties)
+            throws IOException, ParseException, JOSEException {
+        setRsaKeyId(properties.getProperty("rsa.key.id", ""));
+
+        String rsaPublic = properties.getProperty("rsa.public", "");
+        String rsaPrivate = properties.getProperty("rsa.private", "");
+
+        File rsaPublicFile = new File(rsaPublic);
+        JWKSet jwkSet = null;
+        if (rsaPublicFile.exists()) {
+            jwkSet = JWKSet.load(rsaPublicFile);
+        }
+        else {
+            InputStream is =
+                    getClass().getClassLoader().getResourceAsStream(rsaPublic);
+            jwkSet = JWKSet.load(is);
+        }
+        setPublicKeySet(jwkSet);
+
+        File rsaPrivateFile = new File(rsaPrivate);
+        String keyString = null;
+        if (rsaPrivateFile.exists()) {
+            keyString = IOUtils.readFileToString(rsaPrivateFile,
+                    Charset.forName("UTF-8"));
+        }
+        else {
+            InputStream is =
+                    getClass().getClassLoader().getResourceAsStream(rsaPrivate);
+            keyString = IOUtils.readInputStreamToString(is,
+                    Charset.forName("UTF-8"));
+        }
+        RSAKey rsaKey = (RSAKey) JWK.parse(keyString);
+        RSAPrivateKey privateKey = (RSAPrivateKey) rsaKey.toPrivateKey();
+        setRsaPrivateKey(privateKey);
     }
 
     private void setOpenIdConfiguration (Properties properties)
@@ -451,4 +460,28 @@
         this.issuerURI = issuerURI;
     }
 
+    public JWKSet getPublicKeySet () {
+        return publicKeySet;
+    }
+
+    public void setPublicKeySet (JWKSet publicKeySet) {
+        this.publicKeySet = publicKeySet;
+    }
+
+    public RSAPrivateKey getRsaPrivateKey () {
+        return rsaPrivateKey;
+    }
+
+    public void setRsaPrivateKey (RSAPrivateKey rsaPrivateKey) {
+        this.rsaPrivateKey = rsaPrivateKey;
+    }
+
+    public String getRsaKeyId () {
+        return rsaKeyId;
+    }
+
+    public void setRsaKeyId (String rsaKeyId) {
+        this.rsaKeyId = rsaKeyId;
+    }
+
 }
diff --git a/full/src/main/java/de/ids_mannheim/korap/oauth2/openid/service/JWKService.java b/full/src/main/java/de/ids_mannheim/korap/oauth2/openid/service/JWKService.java
new file mode 100644
index 0000000..c157662
--- /dev/null
+++ b/full/src/main/java/de/ids_mannheim/korap/oauth2/openid/service/JWKService.java
@@ -0,0 +1,79 @@
+package de.ids_mannheim.korap.oauth2.openid.service;
+
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.OutputStreamWriter;
+import java.security.KeyPair;
+import java.security.KeyPairGenerator;
+import java.security.NoSuchAlgorithmException;
+import java.security.interfaces.RSAPrivateKey;
+import java.security.interfaces.RSAPublicKey;
+import java.util.UUID;
+
+import org.json.JSONObject;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+
+import com.nimbusds.jose.jwk.JWK;
+import com.nimbusds.jose.jwk.JWKSet;
+import com.nimbusds.jose.jwk.RSAKey;
+
+import de.ids_mannheim.korap.config.FullConfiguration;
+
+@Service
+public class JWKService {
+
+    @Autowired
+    private FullConfiguration config;
+
+    public static void main (String[] args)
+            throws NoSuchAlgorithmException, IOException {
+        generateJWK();
+    }
+
+    public static void generateJWK ()
+            throws NoSuchAlgorithmException, IOException {
+        KeyPairGenerator gen = KeyPairGenerator.getInstance("RSA");
+        gen.initialize(2048);
+        KeyPair keyPair = gen.generateKeyPair();
+
+        // Convert to JWK format
+        JWK jwk = new RSAKey.Builder((RSAPublicKey) keyPair.getPublic())
+                .privateKey((RSAPrivateKey) keyPair.getPrivate())
+                .keyID(UUID.randomUUID().toString()).build();
+
+        // write private key
+        JSONObject json = new JSONObject(jwk.toJSONString());
+        OutputStreamWriter writer = new OutputStreamWriter(
+                new FileOutputStream("kustvakt_rsa.key"));
+        writer.write(json.toString(2));
+        writer.flush();
+        writer.close();
+
+        JWK publicJWK = jwk.toPublicJWK();
+        JWKSet jwkSet = new JWKSet(publicJWK);
+        json = new JSONObject(jwkSet.toString());
+        // write public key
+        writer = new OutputStreamWriter(
+                new FileOutputStream("kustvakt_rsa_public.key"));
+        writer.write(json.toString(2));
+        writer.flush();
+        writer.close();
+    }
+
+    /**
+     * Generates indented JSON string representation of kustvakt
+     * public keys
+     * 
+     * @return json string of kustvakt public keys
+     * 
+     * @see RFC 8017 regarding RSA specifications
+     * @see RFC 7517 regarding JWK (Json Web Key) and JWK Set
+     * 
+     */
+    public String generatePublicKeySetJson () {
+        JWKSet jwkSet = config.getPublicKeySet();
+        JSONObject json = new JSONObject(jwkSet.toString());
+        return json.toString(2);
+    }
+}
diff --git a/full/src/main/java/de/ids_mannheim/korap/oauth2/openid/service/OpenIdTokenService.java b/full/src/main/java/de/ids_mannheim/korap/oauth2/openid/service/OpenIdTokenService.java
index 59f7e67..cedf59b 100644
--- a/full/src/main/java/de/ids_mannheim/korap/oauth2/openid/service/OpenIdTokenService.java
+++ b/full/src/main/java/de/ids_mannheim/korap/oauth2/openid/service/OpenIdTokenService.java
@@ -118,7 +118,7 @@
             SignedJWT idToken = signIdToken(claims,
                     // default
                     new JWSHeader(JWSAlgorithm.RS256),
-                    config.getRSAPrivateKey());
+                    config.getRsaPrivateKey());
             OIDCTokens tokens =
                     new OIDCTokens(idToken, accessToken, refreshToken);
             return new OIDCTokenResponse(tokens);
diff --git a/full/src/main/java/de/ids_mannheim/korap/web/controller/OAuth2WithOpenIdController.java b/full/src/main/java/de/ids_mannheim/korap/web/controller/OAuth2WithOpenIdController.java
index d0e69d7..29b9856 100644
--- a/full/src/main/java/de/ids_mannheim/korap/web/controller/OAuth2WithOpenIdController.java
+++ b/full/src/main/java/de/ids_mannheim/korap/web/controller/OAuth2WithOpenIdController.java
@@ -7,6 +7,7 @@
 
 import javax.servlet.http.HttpServletRequest;
 import javax.ws.rs.Consumes;
+import javax.ws.rs.GET;
 import javax.ws.rs.POST;
 import javax.ws.rs.Path;
 import javax.ws.rs.Produces;
@@ -31,6 +32,7 @@
 
 import de.ids_mannheim.korap.exceptions.KustvaktException;
 import de.ids_mannheim.korap.oauth2.openid.OpenIdHttpRequestWrapper;
+import de.ids_mannheim.korap.oauth2.openid.service.JWKService;
 import de.ids_mannheim.korap.oauth2.openid.service.OpenIdAuthorizationService;
 import de.ids_mannheim.korap.oauth2.openid.service.OpenIdTokenService;
 import de.ids_mannheim.korap.security.context.TokenContext;
@@ -48,6 +50,8 @@
     @Autowired
     private OpenIdTokenService tokenService;
     @Autowired
+    private JWKService jwkService;
+    @Autowired
     private OpenIdResponseHandler openIdResponseHandler;
 
     /**
@@ -92,7 +96,7 @@
      * Class Reference values. </li>
      * </ul>
      * 
-     * @see OpenID Connect Core 1.0 specification
+     * @see "OpenID Connect Core 1.0 specification"
      * 
      * @param request
      * @param context
@@ -184,4 +188,20 @@
         return null;
 
     }
+
+    /**
+     * Retrieves Kustvakt public keys of JWK (Json Web Key) set
+     * format.
+     * 
+     * @return json string representation of the public keys
+     * 
+     * @see "RFC 8017 regarding RSA specifications"
+     * @see "RFC 7517 regarding JWK (Json Web Key) and JWK Set"
+     */
+    @GET
+    @Path("key/public")
+    @Produces(MediaType.APPLICATION_JSON + ";charset=utf-8")
+    public String retrievePublicKeys () {
+        return jwkService.generatePublicKeySetJson();
+    }
 }