Added JWK-set web-controller listing kustvakt public keys.
Change-Id: If8244161d7979008c65e3de5b9154cc5dd427a17
diff --git a/full/Changes b/full/Changes
index b46a483..a8c1c2f 100644
--- a/full/Changes
+++ b/full/Changes
@@ -1,5 +1,5 @@
version 0.60.4
-25/06/2018
+26/06/2018
- implemented OAuth2 authorization code request with OpenID Authentication (margaretha)
- enabled OAuth2 authorization without OpenID authentication using Nimbus library (margaretha)
- implemented response handler for OpenID authentication errors in authorization requests (margaretha)
@@ -8,6 +8,7 @@
- added state to OAuth2 authorization error response (margaretha)
- implemented OpenID token service for authorization code flow (margaretha)
- implemented signed OpenID token with default algorithm RSA256 (margaretha)
+ - added JSON Web Key (JWK) set web-controller listing kustvakt public keys (margaretha)
version 0.60.3
06/06/2018
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();
+ }
}
diff --git a/full/src/test/java/de/ids_mannheim/korap/config/ConfigTest.java b/full/src/test/java/de/ids_mannheim/korap/config/ConfigTest.java
index ed263c0..380d5fb 100644
--- a/full/src/test/java/de/ids_mannheim/korap/config/ConfigTest.java
+++ b/full/src/test/java/de/ids_mannheim/korap/config/ConfigTest.java
@@ -60,7 +60,7 @@
@Test(expected = KustvaktException.class)
@Ignore
- public void testBeanOverrideInjection () throws KustvaktException, URISyntaxException {
+ public void testBeanOverrideInjection () throws Exception {
helper().getContext()
.getConfiguration()
.setPropertiesAsStream(
diff --git a/full/src/test/java/de/ids_mannheim/korap/web/controller/OAuth2OpenIdControllerTest.java b/full/src/test/java/de/ids_mannheim/korap/web/controller/OAuth2OpenIdControllerTest.java
index 6bd081a..5459c6d 100644
--- a/full/src/test/java/de/ids_mannheim/korap/web/controller/OAuth2OpenIdControllerTest.java
+++ b/full/src/test/java/de/ids_mannheim/korap/web/controller/OAuth2OpenIdControllerTest.java
@@ -4,27 +4,17 @@
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertTrue;
-import java.io.BufferedReader;
-import java.io.IOException;
-import java.io.InputStream;
-import java.io.InputStreamReader;
import java.net.URI;
-import java.security.KeyFactory;
import java.security.NoSuchAlgorithmException;
-import java.security.interfaces.RSAPublicKey;
import java.security.spec.InvalidKeySpecException;
-import java.security.spec.KeySpec;
-import java.security.spec.X509EncodedKeySpec;
import java.text.ParseException;
import java.util.Date;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.MultivaluedMap;
-import org.apache.commons.codec.binary.Base64;
import org.apache.http.entity.ContentType;
import org.apache.oltu.oauth2.common.message.types.TokenType;
-import org.junit.BeforeClass;
import org.junit.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.util.MultiValueMap;
@@ -35,6 +25,8 @@
import com.nimbusds.jose.JOSEException;
import com.nimbusds.jose.JWSVerifier;
import com.nimbusds.jose.crypto.RSASSAVerifier;
+import com.nimbusds.jose.jwk.JWKSet;
+import com.nimbusds.jose.jwk.RSAKey;
import com.nimbusds.jwt.SignedJWT;
import com.sun.jersey.api.client.ClientHandlerException;
import com.sun.jersey.api.client.ClientResponse;
@@ -60,19 +52,6 @@
"https://korap.ids-mannheim.de/confidential/redirect";
private String username = "dory";
- private static String publicKey;
-
- @BeforeClass
- public static void init () throws IOException {
- InputStream is = OAuth2OpenIdControllerTest.class.getClassLoader()
- .getResourceAsStream("kustvakt-public.key");
-
- try (BufferedReader reader =
- new BufferedReader(new InputStreamReader(is));) {
- publicKey = reader.readLine();
- }
- }
-
private ClientResponse sendAuthorizationRequest (
MultivaluedMap<String, String> form) throws KustvaktException {
return resource().path("oauth2").path("openid").path("authorize")
@@ -254,7 +233,8 @@
* <li>code id_token token</li>
* </ul>
*
- * @throws KustvaktException
+ * @throws KustvaktExceptiony);
+ * assertTrue(signedJWT.verify(verifier));
*/
@Test
@@ -317,10 +297,8 @@
private void verifyingIdToken (String id_token, String username,
String client_id) throws ParseException, InvalidKeySpecException,
NoSuchAlgorithmException, JOSEException {
- byte[] decodedPuk = Base64.decodeBase64(publicKey);
- KeySpec keySpec = new X509EncodedKeySpec(decodedPuk);
- RSAPublicKey publicKey = (RSAPublicKey) KeyFactory.getInstance("RSA")
- .generatePublic(keySpec);
+ JWKSet keySet = config.getPublicKeySet();
+ RSAKey publicKey = (RSAKey) keySet.getKeyByKeyId(config.getRsaKeyId());
SignedJWT signedJWT = SignedJWT.parse(id_token);
JWSVerifier verifier = new RSASSAVerifier(publicKey);
@@ -334,4 +312,18 @@
assertTrue(new Date()
.before(signedJWT.getJWTClaimsSet().getExpirationTime()));
}
+
+ @Test
+ public void testPublicKeyAPI () throws KustvaktException {
+ ClientResponse response = resource().path("oauth2").path("openid")
+ .path("key").path("public").get(ClientResponse.class);
+ String entity = response.getEntity(String.class);
+ JsonNode node = JsonUtils.readTree(entity);
+ assertEquals(1,node.at("/keys").size());
+ node = node.at("/keys/0");
+ assertEquals("RSA", node.at("/kty").asText());
+ assertEquals(config.getRsaKeyId(), node.at("/kid").asText());
+ assertNotNull(node.at("/e").asText());
+ assertNotNull(node.at("/n").asText());
+ }
}
diff --git a/full/src/test/resources/kustvakt-private.key b/full/src/test/resources/kustvakt-private.key
deleted file mode 100644
index d419b89..0000000
--- a/full/src/test/resources/kustvakt-private.key
+++ /dev/null
@@ -1 +0,0 @@
-MIICeAIBADANBgkqhkiG9w0BAQEFAASCAmIwggJeAgEAAoGBAIlVfcPe+PXGph6BX1zU9HQ1kSt0lz2LIGAB+krHcj5oaWeS/4xicvmmGRE5MeJQEMIcijl3OXjdZR7lK1dxn1UUHuZa3ijMnMgDcQz9BuGg+49R5KdSkkMwlVW5Bdt08TmU9teFdQpg+7bsVGKpSuW6yE6wkgo+Wwufw23ULNkjAgMBAAECgYBVq8o3zTm7gH+SmhwWOhaBBAWaeTH7x3WbzsAHtCG1gsb2QMJAHg4hZJdQokBXMKEzpkAoFxL4Lgxt2IJQG2ZL778uiQiy+xHI8VTXBNXmdo+F3hlNzEmJySSSCxYefSSv+DN/yBrOx0heGXR3vbefXey4a6q8RhthCuRfpHmqmQJBALyFdf4Oj4rozi/KI8yiD71+NNR7hHMtepn3YyY0zBXxk2YEwpcPkzBhdDiL6fYJjjoGFnqKLNqlgO8gHx+ET70CQQC6faQiLjUp50wbEAZqLY7Q353k2qTdAX8W9L2lF/79GEA+EJumQ2iWOu9qYqQuSMSKwheY6mdOVWj8yOMiu2pfAkEAll0cr3aNpw3o5tUjmKPqSgnPuWqLShKMJyHaQy75WMdF+ajyS+pwS7ZvLGrsQQF+H2mbpEFxZTN8kz3blRfDQQJBAKADPdm2HBegRkTSMy7XeDrwI+JBWEPpDMr9o9sMA9XWAQk/5s15+Tstxk9Z49VyynDkqKqkNY+Y6UQ8eedLN7ECQQCDiAsbwOe79EpsHdQBOZeNvpWu1x1TxieN0nCAa/zQz8qupHkL/u8VI8csz+s3qOcgxpJqsn58G8eb9Jmk9fGY
\ No newline at end of file
diff --git a/full/src/test/resources/kustvakt-public.key b/full/src/test/resources/kustvakt-public.key
deleted file mode 100644
index 087d6f7..0000000
--- a/full/src/test/resources/kustvakt-public.key
+++ /dev/null
@@ -1 +0,0 @@
-MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCJVX3D3vj1xqYegV9c1PR0NZErdJc9iyBgAfpKx3I+aGlnkv+MYnL5phkROTHiUBDCHIo5dzl43WUe5StXcZ9VFB7mWt4ozJzIA3EM/QbhoPuPUeSnUpJDMJVVuQXbdPE5lPbXhXUKYPu27FRiqUrlushOsJIKPlsLn8Nt1CzZIwIDAQAB
\ No newline at end of file
diff --git a/full/src/test/resources/kustvakt-test.conf b/full/src/test/resources/kustvakt-test.conf
index ebfb216..48ce51c 100644
--- a/full/src/test/resources/kustvakt-test.conf
+++ b/full/src/test/resources/kustvakt-test.conf
@@ -53,10 +53,15 @@
oauth2.default.scopes = openid read_username read_email
oauth2.client.credentials.scopes = read_client_info
-# JWT
+## JWT
security.jwt.issuer=korap.ids-mannheim.de
-## token expiration
+## JWK
+rsa.private = kustvakt_rsa.key
+rsa.public = kustvakt_rsa_public.key
+rsa.key.id = 74caa3a9-217c-49e6-94e9-2368fdd02c35
+
+## token expiration time
security.longTokenTTL = 1D
security.tokenTTL = 9S
security.shortTokenTTL = 5S
@@ -73,7 +78,7 @@
security.validation.stringLength = 150
security.validation.emailLength = 50
security.encryption.algo=BCRYPT
-security.sharedSecret=testSecret
+security.sharedSecret=testSecretCodeMustContainsMinimum256Bits$87aL2t0sklnf66roGDerNsw2s9
## applicable: rewrite, foundry, filter, deny
security.rewrite.strategies=filter, foundry, rewrite
\ No newline at end of file
diff --git a/full/src/test/resources/kustvakt_rsa.key b/full/src/test/resources/kustvakt_rsa.key
new file mode 100644
index 0000000..1db25e9
--- /dev/null
+++ b/full/src/test/resources/kustvakt_rsa.key
@@ -0,0 +1,12 @@
+{
+ "p": "y7t3f2VRo5TN3IsCjshSWWwe4H1-Xd7iBbtPS_fmBeaVDbLr-05LsGRJxXzKheMJ5DwBzhvWAlCig5uSJG3Gk4i0LgLY5YO33shb9qqqEnF54ZkJbiqxSs5l_dggzZgYB5z0riVl2VA3yfNm1qJIE2eipBouUjBEXMOEtJlOrFc",
+ "kty": "RSA",
+ "q": "v4HHIpOddl_78fVQgvZCsINygpLuniJ3sVShLhX7LnCU0Eb4TMK_Fyz9_JPb3YFvEoPpQw3kfnAhkOBTATTpXzg_dNtR6eQfvDJfHl9R6FuSoVTJoNAO_rqEpKzQOGXl4ohBxVjhXcbEo6GEVp4pZAeXMM8D02IWfvGbJd0Yw0k",
+ "d": "OJFnms4n3ajWKvK26aOh_r8JGgQwbQNIXpx8UqFnc_EB4nzxcLns8-FGKa9Vg3VMAs8cFC4iM9evx1084yqsCeSKgwiV5ZVQkwnp35Gd5BslZxuH8kCdR1mL5y0V0RMwgW-W1ry_YtdhBSIze8XCJXB7udNk7bviiJylEm8OouyxAq-5uUy_qMWYk-mtDSpmPW9SfFf91c6P7-ataDFcd_zxFotd1UwXDVDaPfUnxpOA6Jh1WsvIFhX4IzETuUG8n5C-j6FrK_YlU7U-zFzzF8qWTthQVj5l7A0zOGmq6OC9mv_xtnSc6z9I-HklWFXa8eDsc2JasYqJY8CmTDSy8Q",
+ "e": "AQAB",
+ "kid": "74caa3a9-217c-49e6-94e9-2368fdd02c35",
+ "qi": "Iv8_jAuCTdU7xZ1GXK0Zaql3Azu1-qXiZseod9urLFFZK6OvxrhH0BexG_P1tRikUfEUQiyqNVCU544Z0Y0AdDbgb5aEYNa3Bkb5WAHHXsLDtzXSsxgvR4Pzg3PhT3HTrLkgTlWy9g0u7bwfhb-KTRszcw4SyFXz9o62xJLPJZo",
+ "dp": "pA8_qHhHqMoAiNPsaFyKa_Y0WyTTqPX93w26SnvDYQcRCqoFfCbNrqrj-UOHtw9gfMmRzo795HlYlVCm--zmlxHjvpWOYiyS2bVQ0S8Xq6hztKbPQEbi5FGXMjZkHAuZdi__nWkCPmBpvJfkPX0LO40eHLX0jTzPIEBWUjSOdRs",
+ "dq": "GtnydumlqWRZ6hoQWNx4i1FS6_X4GRoSGD4af2C7oE5Ov0lEJVck_fXkAtcke9FbJohyW2GGSSglvK-HU-L8WcqEMzlRKe8_d97EMXkB_gdg7tf5kV-6yoKSeJh2dYHsErAyMJ5-suxcw-iwqohwm0LpMwHDso7NQq1TqKJwh2k",
+ "n": "mGgmGYIN06ibCh98nsXp0a77xRQNnB9rKpRGKm41tVi0zLQWqmEdDh2CmrMiOOxTJFSlAuAVkwK-KVQZ5Men5dJvRyTwZPtBWSJZk32Znj3VshFloSQlQU-g3oh3c2htP03EDtBLmecZMI-OUV1hRCvrRUrS-qF24CJ-rheFsCmpSievEJDQqTTfXcbAG2DdRQJHWb3y1iyNojB_mV1H2Gztg9DGEZarloqXoTFeDcxs7SpZJqAWCWTJQk8n6Ye79SfGMNrzaaqN9aHx__6FU-GFdZexlWE0CemQcfx_hTEkCTa2EsGgI_GETQIjeCZRB29x91E3AlWVvEgA591pzw"
+}
\ No newline at end of file
diff --git a/full/src/test/resources/kustvakt_rsa_public.key b/full/src/test/resources/kustvakt_rsa_public.key
new file mode 100644
index 0000000..28c2fad
--- /dev/null
+++ b/full/src/test/resources/kustvakt_rsa_public.key
@@ -0,0 +1,6 @@
+{"keys": [{
+ "kty": "RSA",
+ "e": "AQAB",
+ "kid": "74caa3a9-217c-49e6-94e9-2368fdd02c35",
+ "n": "mGgmGYIN06ibCh98nsXp0a77xRQNnB9rKpRGKm41tVi0zLQWqmEdDh2CmrMiOOxTJFSlAuAVkwK-KVQZ5Men5dJvRyTwZPtBWSJZk32Znj3VshFloSQlQU-g3oh3c2htP03EDtBLmecZMI-OUV1hRCvrRUrS-qF24CJ-rheFsCmpSievEJDQqTTfXcbAG2DdRQJHWb3y1iyNojB_mV1H2Gztg9DGEZarloqXoTFeDcxs7SpZJqAWCWTJQk8n6Ye79SfGMNrzaaqN9aHx__6FU-GFdZexlWE0CemQcfx_hTEkCTa2EsGgI_GETQIjeCZRB29x91E3AlWVvEgA591pzw"
+}]}
\ No newline at end of file