diff --git a/security-config/src/main/java/com/networknt/security/JwtConfig.java b/security-config/src/main/java/com/networknt/security/JwtConfig.java index b5956cb4fe..01637800d4 100644 --- a/security-config/src/main/java/com/networknt/security/JwtConfig.java +++ b/security-config/src/main/java/com/networknt/security/JwtConfig.java @@ -16,9 +16,11 @@ package com.networknt.security; import com.networknt.config.Config; +import com.networknt.config.JsonMapper; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import java.util.HashMap; import java.util.Map; /** @@ -29,6 +31,7 @@ public class JwtConfig { private static final Logger logger = LoggerFactory.getLogger(JwtConfig.class); public static final String CONFIG_NAME = "jwt"; + public static final String KEY = "key"; public static final String ISSUER = "issuer"; public static final String AUDIENCE = "audience"; public static final String VERSION = "version"; @@ -42,12 +45,14 @@ public class JwtConfig { String audience; String version; int expiredInMinutes; + Key key; String providerId; private JwtConfig(String configName) { config = Config.getInstance(); mappedConfig = config.getJsonMapConfigNoCache(configName); setConfigData(); + setConfigMap(); } public static JwtConfig load() { return new JwtConfig(CONFIG_NAME); @@ -97,6 +102,14 @@ public void setExpiredInMinutes(int expiredInMinutes) { this.expiredInMinutes = expiredInMinutes; } + public Key getKey() { + return key; + } + + public void setKey(Key key) { + this.key = key; + } + public String getProviderId() { return providerId; } public void setProviderId(String providerId) { this.providerId = providerId; } @@ -123,4 +136,64 @@ private void setConfigData() { } } + private void setConfigMap() { + if(getMappedConfig() != null) { + Object object = getMappedConfig().get(KEY); + if(object != null) { + if(object instanceof Map) { + key = Config.getInstance().getMapper().convertValue(object, Key.class); + } else if(object instanceof String) { + try { + key = Config.getInstance().getMapper().readValue((String)object, Key.class); + } catch (Exception e) { + logger.error("Exception:", e); + } + } else { + logger.error("key in jwt.yml is not a map or string"); + } + } + } + } + + public static class Key { + String kid; + String filename; + String password; + String keyName; + + public Key() { + } + + public String getKid() { + return kid; + } + + public void setKid(String kid) { + this.kid = kid; + } + + public String getFilename() { + return filename; + } + + public void setFilename(String filename) { + this.filename = filename; + } + + public String getPassword() { + return password; + } + + public void setPassword(String password) { + this.password = password; + } + + public String getKeyName() { + return keyName; + } + + public void setKeyName(String keyName) { + this.keyName = keyName; + } + } } diff --git a/security-config/src/main/java/com/networknt/security/JwtVerifier.java b/security-config/src/main/java/com/networknt/security/JwtVerifier.java index 52d63b0a0d..7923da982a 100644 --- a/security-config/src/main/java/com/networknt/security/JwtVerifier.java +++ b/security-config/src/main/java/com/networknt/security/JwtVerifier.java @@ -21,11 +21,13 @@ import com.networknt.client.oauth.OauthHelper; import com.networknt.client.oauth.SignKeyRequest; import com.networknt.client.oauth.TokenKeyRequest; +import com.networknt.config.Config; import com.networknt.config.ConfigException; import com.networknt.config.JsonMapper; import com.networknt.exception.ClientException; import com.networknt.exception.ExpiredTokenException; import com.networknt.status.Status; +import com.networknt.utility.FingerPrintUtil; import org.jose4j.jwk.JsonWebKey; import org.jose4j.jwk.JsonWebKeySet; import org.jose4j.jwt.JwtClaims; @@ -37,16 +39,22 @@ import org.jose4j.keys.resolvers.VerificationKeyResolver; import org.jose4j.keys.resolvers.X509VerificationKeyResolver; import org.jose4j.lang.JoseException; +import org.owasp.encoder.Encode; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import java.io.ByteArrayInputStream; +import java.io.IOException; +import java.io.InputStream; import java.nio.charset.StandardCharsets; import java.security.cert.CertificateException; import java.security.cert.CertificateFactory; import java.security.cert.X509Certificate; import java.util.*; +import java.util.concurrent.TimeUnit; import java.util.function.BiFunction; +import java.util.regex.Pattern; +import java.util.stream.Stream; /** * This is a new class that is designed as non-static to replace the JwtHelper which is a static class. The reason @@ -84,6 +92,7 @@ public class JwtVerifier extends TokenVerifier { static Map certMap; static String audience; // this is the audience from the client.yml with single oauth provider. static Map audienceMap; // this is the audience map from the client.yml with multiple oauth providers. + static List fingerPrints; public JwtVerifier(SecurityConfig cfg) { config = cfg; @@ -94,6 +103,9 @@ public JwtVerifier(SecurityConfig cfg) { // init getting JWK during the initialization. The other part is in the resolver for OAuth 2.0 provider to // rotate keys when the first token is received with the new kid. String keyResolver = config.getKeyResolver(); + + this.cacheCertificates(); + // if KeyResolver is jwk and bootstrap from jwk is true, load jwk during server startup. if(logger.isTraceEnabled()) logger.trace("keyResolver = {} bootstrapFromKeyService = {}", keyResolver, bootstrapFromKeyService); @@ -102,6 +114,63 @@ public JwtVerifier(SecurityConfig cfg) { } } + + /** + * Caches cert. + */ + private void cacheCertificates() { + // cache the certificates + certMap = new HashMap<>(); + fingerPrints = new ArrayList<>(); + if (config.getCertificate() != null) { + Map keyMap = config.getCertificate(); + for (String kid : keyMap.keySet()) { + X509Certificate cert = null; + try { + cert = readCertificate((String) keyMap.get(kid)); + } catch (Exception e) { + logger.error("Exception:", e); + } + certMap.put(kid, cert); + fingerPrints.add(FingerPrintUtil.getCertFingerPrint(cert)); + } + } + logger.debug("Successfully cached Certificate"); + } + + /** + * Read certificate from a file and convert it into X509Certificate object + * + * @param filename certificate file name + * @return X509Certificate object + * @throws Exception Exception while reading certificate + */ + public X509Certificate readCertificate(String filename) + throws Exception { + InputStream inStream = null; + X509Certificate cert = null; + try { + inStream = Config.getInstance().getInputStreamFromFile(filename); + if (inStream != null) { + CertificateFactory cf = CertificateFactory.getInstance("X.509"); + cert = (X509Certificate) cf.generateCertificate(inStream); + } else { + logger.info("Certificate " + Encode.forJava(filename) + " not found."); + } + } catch (Exception e) { + logger.error("Exception: ", e); + } finally { + if (inStream != null) { + try { + inStream.close(); + } catch (IOException ioe) { + logger.error("Exception: ", ioe); + } + } + } + return cert; + } + /** * This method is to keep backward compatible for those call without VerificationKeyResolver. The single * auth server is used in this case. @@ -716,4 +785,18 @@ public X509Certificate getCertForSign(String kid) { } return certificate; } + + /** + * Get a list of certificate fingerprints for server info endpoint so that certification process in light-portal + * can detect if your service still use the default public key certificates provided by the light-4j framework. + *

+ * The default public key certificates are for dev only and should be replaced on any other environment or + * set bootstrapFromKeyService: true if you are using light-oauth2 so that key can be dynamically loaded. + * + * @return List of certificate fingerprints + */ + public List getFingerPrints() { + return fingerPrints; + } + } diff --git a/security-config/src/main/resources/config/jwt.yml b/security-config/src/main/resources/config/jwt.yml index 5819e7f97b..0a43a68aac 100644 --- a/security-config/src/main/resources/config/jwt.yml +++ b/security-config/src/main/resources/config/jwt.yml @@ -1,5 +1,11 @@ # This is the default JWT configuration and some of the properties need to be overwritten when used to issue JWT tokens. # It is a component that used to issue JWT token. Normally, it should be used by light-oauth2 or oauth-kafka only. +# Signature private key that used to sign JWT tokens. It is here to ensure backward compatibility only. +key: ${jwt.key:{"kid":"100","filename":"primary.jks","keyName":"selfsigned","password":"password"}} + # kid: '100' # kid that used to sign the JWT tokens. It will be shown up in the token header. + # filename: "primary.jks" # private key that is used to sign JWT tokens. + # keyName: selfsigned # key name that is used to identify the right key in keystore. + # password: password # private key store password and private key password is the same # issuer of the JWT token issuer: ${jwt.issuer:urn:com:networknt:oauth2:v1} # audience of the JWT token diff --git a/security/src/main/java/com/networknt/security/JwtIssuer.java b/security/src/main/java/com/networknt/security/JwtIssuer.java index 25b6df78bc..70a589437f 100644 --- a/security/src/main/java/com/networknt/security/JwtIssuer.java +++ b/security/src/main/java/com/networknt/security/JwtIssuer.java @@ -25,6 +25,8 @@ import java.security.KeyStore; import java.security.PrivateKey; +import java.security.interfaces.RSAPrivateKey; +import java.util.Map; /** * JWT token issuer helper utility that use by light-ouath2 token and code services to @@ -36,6 +38,55 @@ public class JwtIssuer { private static final Logger logger = LoggerFactory.getLogger(JwtIssuer.class); private static final JwtConfig jwtConfig = JwtConfig.load(); + /** + * A static method that generate JWT token from JWT claims object. This method is deprecated, and it + * is replaced by the method that takes kid and private key as parameters. + * + * @param claims JwtClaims object + * @return A string represents jwt token + * @throws JoseException JoseException + */ + @Deprecated + public static String getJwt(JwtClaims claims) throws JoseException { + String jwt; + RSAPrivateKey privateKey = (RSAPrivateKey) getPrivateKey( + jwtConfig.getKey().getFilename(),jwtConfig.getKey().getPassword(), jwtConfig.getKey().getKeyName()); + + // A JWT is a JWS and/or a JWE with JSON claims as the payload. + // In this example it is a JWS nested inside a JWE + // So we first create a JsonWebSignature object. + JsonWebSignature jws = new JsonWebSignature(); + + // The payload of the JWS is JSON content of the JWT Claims + jws.setPayload(claims.toJson()); + + // The JWT is signed using the sender's private key + jws.setKey(privateKey); + + // Get provider from security config file, it should be two digit + // And the provider id will set as prefix for keyid in the token header, for example: 05100 + // if there is no provider id, we use "00" for the default value + String provider_id = ""; + if (jwtConfig.getProviderId() != null) { + provider_id = jwtConfig.getProviderId(); + if (provider_id.length() == 1) { + provider_id = "0" + provider_id; + } else if (provider_id.length() > 2) { + logger.error("provider_id defined in the security.yml file is invalid; the length should be 2"); + provider_id = provider_id.substring(0, 2); + } + } + jws.setKeyIdHeaderValue(provider_id + jwtConfig.getKey().getKid()); + + // Set the signature algorithm on the JWT/JWS that will integrity protect the claims + jws.setAlgorithmHeaderValue(AlgorithmIdentifiers.RSA_USING_SHA256); + + // Sign the JWS and produce the compact serialization, which will be the inner JWT/JWS + // representation, which is a string consisting of three dot ('.') separated + // base64url-encoded parts in the form Header.Payload.Signature + jwt = jws.getCompactSerialization(); + return jwt; + } /** * A static method that generate JWT token from JWT claims object and a given private key. This private key diff --git a/security/src/main/java/com/networknt/security/JwtMockHandler.java b/security/src/main/java/com/networknt/security/JwtMockHandler.java new file mode 100644 index 0000000000..8c8b5b988f --- /dev/null +++ b/security/src/main/java/com/networknt/security/JwtMockHandler.java @@ -0,0 +1,68 @@ +/* + * Copyright (c) 2016 Network New Technologies Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.networknt.security; + +import com.networknt.config.Config; +import com.networknt.handler.LightHttpHandler; +import io.undertow.server.HttpHandler; +import io.undertow.server.HttpServerExchange; +import io.undertow.util.Headers; +import org.jose4j.jwt.JwtClaims; + +import java.nio.ByteBuffer; +import java.util.Arrays; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +/** + * This is a jwt token provider for testing only. It should be injected into the server + * after it is started. Do not use it on production runtime. If you need an external + * OAuth2 server, please take a look at https://github.com/networknt/light-oauth2 + * + * @author Steve Hu + */ +public class JwtMockHandler implements LightHttpHandler { + + public static final String ENABLE_MOCK_JWT = "enableMockJwt"; + + public JwtMockHandler() {} + + @Override + public void handleRequest(HttpServerExchange exchange) throws Exception { + exchange.getResponseHeaders().put( + Headers.CONTENT_TYPE, "application/json"); + + Map resMap = new HashMap<>(); + resMap.put("access_token", JwtIssuer.getJwt(mockClaims())); + resMap.put("token_type", "bearer"); + resMap.put("expires_in", 600); + exchange.getResponseSender().send(ByteBuffer.wrap( + Config.getInstance().getMapper().writeValueAsBytes( + resMap))); + } + + public JwtClaims mockClaims() { + JwtClaims claims = JwtIssuer.getDefaultJwtClaims(); + claims.setClaim("user_id", "steve"); + claims.setClaim("user_type", "EMPLOYEE"); + claims.setClaim("client_id", "aaaaaaaa-1234-1234-1234-bbbbbbbb"); + List scope = Arrays.asList("api.r", "api.w"); + claims.setStringListClaim("scope", scope); // multi-valued claims work too and will end up as a JSON array + return claims; + } +} diff --git a/security/src/test/java/com/networknt/security/JwtVerifierSingleJwkTest.java b/security/src/test/java/com/networknt/security/JwtVerifierSingleJwkTest.java index 77bd453e8e..b7cdaa0ac9 100644 --- a/security/src/test/java/com/networknt/security/JwtVerifierSingleJwkTest.java +++ b/security/src/test/java/com/networknt/security/JwtVerifierSingleJwkTest.java @@ -417,4 +417,31 @@ public void testVerifyToken() throws Exception { System.out.println("jwtClaims = " + claims); } + /** + * This test needs light-oauth2 service to be up and running in order to test it + * to start the light-oauth2 please refer to https://networknt.github.io/light-oauth2/tutorials + */ + @Test + @Ignore + public void testGetCertForToken() { + JwtVerifier jwtVerifier = new JwtVerifier(securityConfig); + X509Certificate certificate = jwtVerifier.getCertForToken("100"); + System.out.println("certificate = " + certificate); + Assert.assertNotNull(certificate); + } + + /** + * This test needs light-oauth2 service to be up and running in order to test it + * to start the light-oauth2 please refer to https://networknt.github.io/light-oauth2/tutorials + */ + @Test + @Ignore + public void testGetCertForSign() { + JwtVerifier jwtVerifier = new JwtVerifier(securityConfig); + X509Certificate certificate = jwtVerifier.getCertForSign("100"); + System.out.println("certificate = " + certificate); + Assert.assertNotNull(certificate); + } + + } diff --git a/security/src/test/java/com/networknt/security/JwtVerifierTest.java b/security/src/test/java/com/networknt/security/JwtVerifierTest.java index 7b65592fb2..0650f25fc4 100644 --- a/security/src/test/java/com/networknt/security/JwtVerifierTest.java +++ b/security/src/test/java/com/networknt/security/JwtVerifierTest.java @@ -36,7 +36,49 @@ import java.util.*; public class JwtVerifierTest extends JwtVerifierJwkBase { + static final String CONFIG_NAME_509 = "security-509"; + static final String CONFIG_NAME_OPENAPI = "openapi-security-no-default-jwtcertificate"; static final String CONFIG_NAME = "security"; + static final String CONFIG_RELAXED_VERIFICATION = "security-relaxedVerification"; + @Test + public void testReadCertificate() { + SecurityConfig config = SecurityConfig.load(CONFIG_NAME); + Map keyMap = config.getCertificate(); + Map certMap = new HashMap<>(); + JwtVerifier jwtVerifier = new JwtVerifier(config); + for(String kid: keyMap.keySet()) { + X509Certificate cert = null; + try { + cert = jwtVerifier.readCertificate((String)keyMap.get(kid)); + } catch (Exception e) { + e.printStackTrace(); + } + certMap.put(kid, cert); + } + Assert.assertEquals(2, certMap.size()); + } + + @Test + public void testReadCertificate2() { + SecurityConfig config = SecurityConfig.load(CONFIG_NAME_OPENAPI); + Map certMap = new HashMap<>(); + if (config.getCertificate()!=null) { + Map keyMap = config.getCertificate(); + JwtVerifier jwtVerifier = new JwtVerifier(config); + for(String kid: keyMap.keySet()) { + X509Certificate cert = null; + try { + cert = jwtVerifier.readCertificate((String)keyMap.get(kid)); + } catch (Exception e) { + e.printStackTrace(); + } + certMap.put(kid, cert); + } + } + + Assert.assertEquals(0, certMap.size()); + } + @Test public void testVerifyJwtByJsonWebKeys() throws Exception { Key privateKey = KeyUtil.deserializePrivateKey(curr_key, KeyUtil.RSA); @@ -105,6 +147,113 @@ public void testGenerateJsonWebKeys() throws Exception { System.out.println(jwksJson); } + private static KeyStore loadKeystore(String fileName, String keyStorePass) throws Exception { + KeyStore keystore = KeyStore.getInstance(KeyStore.getDefaultType()); + char[] passwd = keyStorePass.toCharArray(); + keystore.load(Config.getInstance().getInputStreamFromFile(fileName), passwd); + return keystore; + } + + @Test + public void testVerifyJwt() throws Exception { + JwtClaims claims = ClaimsUtil.getTestClaims("steve", "EMPLOYEE", "f7d42348-c647-4efb-a52d-4c5787421e72", Arrays.asList("write:pets", "read:pets"), "user"); + String jwt = JwtIssuer.getJwt(claims); + claims = null; + Assert.assertNotNull(jwt); + JwtVerifier jwtVerifier = new JwtVerifier(SecurityConfig.load(CONFIG_NAME_509)); + try { + claims = jwtVerifier.verifyJwt(jwt, false, true); + } catch (Exception e) { + e.printStackTrace(); + } + Assert.assertNotNull(claims); + Assert.assertEquals("steve", claims.getStringClaimValue(Constants.USER_ID_STRING)); + + try { + claims = jwtVerifier.verifyJwt(jwt, false, true); + } catch (Exception e) { + e.printStackTrace(); + } + + System.out.println("jwtClaims = " + claims); + } + + @Test + public void testVerifySign() throws Exception { + JwtClaims claims = ClaimsUtil.getTestClaims("steve", "EMPLOYEE", "f7d42348-c647-4efb-a52d-4c5787421e72", Arrays.asList("write:pets", "read:pets"), "user"); + String jwt = JwtIssuer.getJwt(claims); + claims = null; + Assert.assertNotNull(jwt); + JwtVerifier jwtVerifier = new JwtVerifier(SecurityConfig.load(CONFIG_NAME_509)); + try { + claims = jwtVerifier.verifyJwt(jwt, false, false); + } catch (Exception e) { + e.printStackTrace(); + } + Assert.assertNotNull(claims); + Assert.assertEquals("steve", claims.getStringClaimValue(Constants.USER_ID_STRING)); + + try { + claims = jwtVerifier.verifyJwt(jwt, false, false); + } catch (Exception e) { + e.printStackTrace(); + } + + System.out.println("jwtClaims = " + claims); + } + + @Test + public void testVerifyToken() throws Exception { + JwtClaims claims = ClaimsUtil.getTestClaims("steve", "EMPLOYEE", "f7d42348-c647-4efb-a52d-4c5787421e72", Arrays.asList("write:pets", "read:pets"), "user"); + String jwt = JwtIssuer.getJwt(claims); + claims = null; + Assert.assertNotNull(jwt); + JwtVerifier jwtVerifier = new JwtVerifier(SecurityConfig.load(CONFIG_NAME_509)); + try { + claims = jwtVerifier.verifyJwt(jwt, false, true); + } catch (Exception e) { + e.printStackTrace(); + } + Assert.assertNotNull(claims); + Assert.assertEquals("steve", claims.getStringClaimValue(Constants.USER_ID_STRING)); + + try { + claims = jwtVerifier.verifyJwt(jwt, false, true); + } catch (Exception e) { + e.printStackTrace(); + } + + System.out.println("jwtClaims = " + claims); + } + + @Test + public void testRelaxedKeyValidation() throws Exception { + String jwt = "eyJraWQiOiJ7XCJwcm92aWRlcl90eXBlXCI6XCJkYlwiLFwiYWxpYXNcIjpcImtleXRlc3RcIixcInR5cGVcIjpcImxvY2FsXCIsXCJ2ZXJzaW9uXCI6XCIxXCJ9IiwiYWxnIjoiUlMyNTYifQ" + + "." + + "eyJzdWIiOiJDT05TVU1FUlxcMTYwMTIzMDIxNjUzIiwicGFydHlJZCI6IjMxNzMyNDk3IiwiZXhwIjoxNjA2NDk5NjgzLCJpYXQiOjE2MDY0OTg3ODMsImp0aSI6IjAzYjA5YmY3LTFlMzktNDNlOC05NDAwLWQzYTcxMDkwYjMxNCJ9" + + "." + + "ob9aglgNHcdr2wYXnhwL_P-m76MCd2tZyRJWl9GIhRaZkR_FYoQTXQKz3WINGwn4aynzmkqZx28HlhKttA-A4WQNwEgETFOoq1tCrZ9ZQOrxaexccYdhLCuqzNllDD8OfXm-vFLp52-UpLvVIr3ySPvv9d034IhSw38EkZmV0Vc"; + + JwtClaims claims = null; + + /* config points to our test pub cert for this JWT */ + JwtVerifier jwtVerifier = new JwtVerifier(SecurityConfig.load(CONFIG_RELAXED_VERIFICATION)); + + Assert.assertTrue(jwtVerifier.enableRelaxedKeyValidation); + + try { + claims = jwtVerifier.verifyJwt(jwt, true, true); + } catch (Exception e) { + e.printStackTrace(); + } + + /* assert that our claims are not null, and that we can find the claim 'sub' */ + Assert.assertNotNull(claims); + Assert.assertEquals("CONSUMER\\160123021653", claims.getStringClaimValue("sub")); + System.out.println("jwt = " + jwt); + System.out.println("jwtClaims = " + claims); + } + @Test public void testStringList() { List ids = new ArrayList<>(); diff --git a/security/src/test/resources/config/primary.crt b/security/src/test/resources/config/primary.crt new file mode 100644 index 0000000000..51ee364975 --- /dev/null +++ b/security/src/test/resources/config/primary.crt @@ -0,0 +1,19 @@ +-----BEGIN CERTIFICATE----- +MIIDmzCCAoOgAwIBAgIEHnAgtDANBgkqhkiG9w0BAQsFADB+MQswCQYDVQQGEwJDQTEQMA4GA1UE +CBMHT250YXJpbzEUMBIGA1UEBxMLTWlzc2lzc2F1Z2ExJjAkBgNVBAoTHU5ldHdvcmsgTmV3IFRl +Y2hub2xvZ2llcyBJbmMuMQwwCgYDVQQLEwNERVYxETAPBgNVBAMTCFN0ZXZlIEh1MB4XDTE2MDkw +MTE2MTYxNVoXDTI2MDcxMTE2MTYxNVowfjELMAkGA1UEBhMCQ0ExEDAOBgNVBAgTB09udGFyaW8x +FDASBgNVBAcTC01pc3Npc3NhdWdhMSYwJAYDVQQKEx1OZXR3b3JrIE5ldyBUZWNobm9sb2dpZXMg +SW5jLjEMMAoGA1UECxMDREVWMREwDwYDVQQDEwhTdGV2ZSBIdTCCASIwDQYJKoZIhvcNAQEBBQAD +ggEPADCCAQoCggEBALrlxMtDb60DogElf4TBz504tRheZimAE0dJL/Yby4nacJdqvc5l4z+WWpDf +rI9krQ2Yi9yvhwAP+PrR6gWcIqWP4cpNE7XIAUDgr4CtyI7CptT/lpjtbkz4DGCMmaeDn0jqHqJt +SeSZGfwVu5zAGm8n4sHatjnnxBI/iWzkTII3V4xv0WeK37szNTEd+ly2ag7n2IV5zNnYmqZTeMQm +J2ENS+IwAG3ENtiVtrVTx/2bGtqutJjtdxsN58/cUG/guRyMT6OPI8Yi3ZzevdvRbxadyhEl/Kaw +6vJcdxmJI3tp4lx+p6sAxOWa7aapJe4JxutAQqzv0GKdVjoHKQ1wB60CAwEAAaMhMB8wHQYDVR0O +BBYEFIPF9SBd06RWU1eDL73CKfy01lavMA0GCSqGSIb3DQEBCwUAA4IBAQAoaKZGOak3Upz/ordF +slZoJuZlCu7jnKQEjYwHf3DNxcd1WmgFPtMcna6pW0VUxPIfidEA6VCMsGoK1RvshB0SjrRdCht6 +5qPXs9kV3NW0WvMiwDSYZZ9HgaZ9efTe5E9Fzc7ltKrE43L6k8NJcaEEWEdpdjFbrAqH4I+j/Vro +K3OhIo062fXjas5ipL4gF+3ECImjWzirQP8UiAfM0/36x7rtAu3btH/qI9hSyx39LBPPE5AsDJZ4 +dSMwNTW1gqmBAZIj+zQ/RD5dyWfPwON7Q+t96YbK6WBuYo0xy+I+PjcUgrWYWP3N24hlq8ZBIei+ +BudoEVJlIlmS0aRCuP8n +-----END CERTIFICATE----- diff --git a/security/src/test/resources/config/primary.jks b/security/src/test/resources/config/primary.jks new file mode 100644 index 0000000000..0d6ae7cf44 Binary files /dev/null and b/security/src/test/resources/config/primary.jks differ diff --git a/security/src/test/resources/config/secondary.crt b/security/src/test/resources/config/secondary.crt new file mode 100644 index 0000000000..215cedb42b --- /dev/null +++ b/security/src/test/resources/config/secondary.crt @@ -0,0 +1,19 @@ +-----BEGIN CERTIFICATE----- +MIIDkzCCAnugAwIBAgIEUBGbJDANBgkqhkiG9w0BAQsFADB6MQswCQYDVQQGEwJDQTEQMA4GA1UE +CBMHT250YXJpbzEQMA4GA1UEBxMHVG9yb250bzEmMCQGA1UEChMdTmV0d29yayBOZXcgVGVjaG5v +bG9naWVzIEluYy4xDDAKBgNVBAsTA0FQSTERMA8GA1UEAxMIU3RldmUgSHUwHhcNMTYwOTIyMjI1 +OTIxWhcNMjYwODAxMjI1OTIxWjB6MQswCQYDVQQGEwJDQTEQMA4GA1UECBMHT250YXJpbzEQMA4G +A1UEBxMHVG9yb250bzEmMCQGA1UEChMdTmV0d29yayBOZXcgVGVjaG5vbG9naWVzIEluYy4xDDAK +BgNVBAsTA0FQSTERMA8GA1UEAxMIU3RldmUgSHUwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEK +AoIBAQCqYfarFwug2DwpG/mmcW77OluaHVNsKEVJ/BptLp5suJAH/Z70SS5pwM4x2QwMOVO2ke8U +rsAws8allxcuKXrbpVt4evpO1Ly2sFwqB1bjN3+VMp6wcT+tSjzYdVGFpQAYHpeA+OLuoHtQyfpB +0KCveTEe3KAG33zXDNfGKTGmupZ3ZfmBLINoey/X13rY71ITt67AY78VHUKb+D53MBahCcjJ9YpJ +UHG+Sd3d4oeXiQcqJCBCVpD97awWARf8WYRIgU1xfCe06wQ3CzH3+GyfozLeu76Ni5PwE1tm7Dhg +EDSSZo5khmzVzo4G0T2sOeshePc5weZBNRHdHlJA0L0fAgMBAAGjITAfMB0GA1UdDgQWBBT9rnek +spnrFus5wTszjdzYgKll9TANBgkqhkiG9w0BAQsFAAOCAQEAT8udTfUGBgeWbN6ZAXRI64VsSJj5 +1sNUN1GPDADLxZF6jArKU7LjBNXn9bG5VjJqlx8hQ1SNvi/t7FqBRCUt/3MxDmGZrVZqLY1kZ2e7 +x+5RykbspA8neEUtU8sOr/NP3O5jBjU77EVec9hNNT5zwKLevZNL/Q5mfHoc4GrIAolQvi/5fEqC +8OMdOIWS6sERgjaeI4tXxQtHDcMo5PeLW0/7t5sgEsadZ+pkdeEMVTmLfgf97bpNNI7KF5uEbYnQ +NpwCT+NNC5ACmJmKidrfW23kml1C7vr7YzTevw9QuH/hN8l/Rh0fr+iPEVpgN6Zv00ymoKGmjuuW +owVmdKg/0w== +-----END CERTIFICATE----- diff --git a/security/src/test/resources/config/secondary.jks b/security/src/test/resources/config/secondary.jks new file mode 100644 index 0000000000..33824c89a9 Binary files /dev/null and b/security/src/test/resources/config/secondary.jks differ diff --git a/security/src/test/resources/config/security-509.yml b/security/src/test/resources/config/security-509.yml new file mode 100644 index 0000000000..18adbdb7ac --- /dev/null +++ b/security/src/test/resources/config/security-509.yml @@ -0,0 +1,67 @@ +# Security configuration for security module in light-4j. For each individual framework, +# it has a framework specific security config file to control if security and scopes +# verification are enabled or not. + +# This configuration file is only for JwtHelper class most of the cases. However, if there +# is no framework specific security configuration available. The fallback security config +# is read from this file. Hence we leave the enableVerifyJwt and enableVerifyScope to true. +--- +# Enable JWT verification flag. +enableVerifyJwt: true + +# Enable JWT scope verification. Only valid when enableVerifyJwt is true. +enableVerifyScope: true + +# If set true, the JWT verifier handler will pass if the JWT token is expired already. Unless +# you have a strong reason, please use it only on the dev environment if your OAuth 2 provider +# doesn't support long-lived token for dev environment or test automation. +ignoreJwtExpiry: ${security.ignoreJwtExpiry:false} + +# Enables relaxed verification for jwt. e.g. Disables key length requirements. +# Should be used in test environments only. +enableRelaxedKeyValidation: ${security.enableRelaxedKeyValidation:false} + +# User for test only. should be always be false on official environment. +enableMockJwt: false + +# JWT signature public certificates. kid and certificate path mappings. +jwt: + certificate: + '100': primary.crt + '101': secondary.crt + clockSkewInSeconds: 60 + # Key distribution server standard: JsonWebKeySet for other OAuth 2.0 provider| X509Certificate for light-oauth2 + keyResolver: X509Certificate +# Enable or disable JWT token logging for audit. This is to log the entire token +# or choose the next option that only logs client_id, user_id and scope. +logJwtToken: true + +# Enable or disable client_id, user_id and scope logging if you don't want to log +# the entire token. Choose this option or the option above. +logClientUserScope: false + +# Enable JWT token cache to speed up verification. This will only verify expired time +# and skip the signature verification as it takes more CPU power and long time. If +# each request has a different jwt token like authorization code flow, this indicator +# should be turned off. Otherwise, the cached jwt will only be removed after 15 minutes +# and the cache can grow bigger if the number of the requests are very high. This will +# cause memory kill in a Kubernetes pod if the memory setting is limited. +enableJwtCache: true + +# If enableJwtCache is true, then an error message will be shown up in the log if the +# cache size is bigger than the jwtCacheFullSize. This helps the developers to detect +# cache problem if many distinct tokens flood the cache in a short period time. If you +# see JWT cache exceeds size limit in logs, you need to turn off the enableJwtCache or +# increase the cache full size to a bigger number from the default 100. +jwtCacheFullSize: 100 + +# If you are using light-oauth2, then you don't need to have oauth subfolder for public +# key certificate to verify JWT token, the key will be retrieved from key endpoint once +# the first token is arrived. Default to false for dev environment without oauth2 server +# or official environment that use other OAuth 2.0 providers. +bootstrapFromKeyService: false + +# Used in light-oauth2 and oauth-kafka key service for federated deployment. Each instance +# will have a providerId, and it will be part of the kid to allow each instance to get the +# JWK from other instance based on the providerId in the kid. +providerId: ${security.providerId:}