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();
+ }
}