From 9f8273f09eef10063b419ee2204c7a8e805c31f7 Mon Sep 17 00:00:00 2001 From: Dmytro Stonaiev Date: Tue, 7 Apr 2026 15:34:59 -0700 Subject: [PATCH 1/2] Added keystore & truststore properties Added keystore and truststore driver connection properties support --- .../jdbc/client/impl/RedisJedisURIBase.java | 56 ++++-- .../RedisDriverPropertyInfoHelper.java | 13 ++ driver/src/main/java/jdbc/utils/SSLUtils.java | 166 +++++++++++++----- 3 files changed, 177 insertions(+), 58 deletions(-) diff --git a/driver/src/main/java/jdbc/client/impl/RedisJedisURIBase.java b/driver/src/main/java/jdbc/client/impl/RedisJedisURIBase.java index c0a9114..404ea3d 100644 --- a/driver/src/main/java/jdbc/client/impl/RedisJedisURIBase.java +++ b/driver/src/main/java/jdbc/client/impl/RedisJedisURIBase.java @@ -21,6 +21,7 @@ import static jdbc.properties.RedisDefaultConfig.CONFIG; import static jdbc.properties.RedisDriverPropertyInfoHelper.*; import static jdbc.utils.SSLUtils.getTrustEverybodySSLContext; +import static jdbc.utils.SSLUtils.getValidatingSSLContext; import static jdbc.utils.Utils.*; public abstract class RedisJedisURIBase implements JedisClientConfig { @@ -104,8 +105,20 @@ protected RedisJedisURIBase(String url, Properties info) throws SQLException { return url.replaceFirst(prefix, ""); } - protected abstract @NotNull String getPrefix(); + private String validateAndGetUrl(String path) { + String url = path; + if (!isNullOrEmpty(url)) { + try { + new URL(url); + } + catch (MalformedURLException e) { + url = "file:" + url; + } + } + return url; + } + protected abstract @NotNull String getPrefix(); private void setAuth(@NotNull String authBlock, Properties info) { String user = CONFIG.getUser(); @@ -166,20 +179,37 @@ private void setSSLParameters(@NotNull Map parameters, Propertie ssl = getBoolean(parameters, info, SSL, CONFIG.isSsl()); if (ssl) { boolean verifyServerCertificate = getBoolean(parameters, info, VERIFY_SERVER_CERTIFICATE, CONFIG.isVerifyServerCertificate()); + + // Read keystore properties for client certificate authentication (used in both branches) + String keystorePath = getString(parameters, info, KEYSTORE_PATH, System.getProperty("javax.net.ssl.keyStore", "")); + String keystorePassword + = getString(parameters, info, KEYSTORE_PASSWORD, System.getProperty("javax.net.ssl.keyStorePassword", "")); + String keystoreType = getString(parameters, info, KEYSTORE_TYPE, + System.getProperty("javax.net.ssl.keyStoreType", KeyStore.getDefaultType())); + String keystoreUrl = validateAndGetUrl(keystorePath); + if (!verifyServerCertificate) { - String keyStoreType = System.getProperty("javax.net.ssl.keyStoreType", KeyStore.getDefaultType()); - String keyStorePassword = System.getProperty("javax.net.ssl.keyStorePassword", ""); - String keyStoreUrl = System.getProperty("javax.net.ssl.keyStore", ""); - // check keyStoreUrl - if (!isNullOrEmpty(keyStoreUrl)) { - try { - new URL(keyStoreUrl); - } catch (MalformedURLException e) { - keyStoreUrl = "file:" + keyStoreUrl; - } - } - SSLContext context = getTrustEverybodySSLContext(keyStoreUrl, keyStoreType, keyStorePassword); + // For verifyServerCertificate=false, create custom SSLContext that trusts everything + // but still loads client certificate from keystore if provided + SSLContext context = getTrustEverybodySSLContext(keystoreUrl, keystoreType, keystorePassword); sslSocketFactory = context.getSocketFactory(); + } else { + // Read truststore properties (only needed for verifyServerCertificate=true) + String truststorePath = getString(parameters, info, TRUSTSTORE_PATH, System.getProperty("javax.net.ssl.trustStore", "")); + String truststorePassword + = getString(parameters, info, TRUSTSTORE_PASSWORD, System.getProperty("javax.net.ssl.trustStorePassword", "")); + String truststoreType = getString(parameters, info, TRUSTSTORE_TYPE, + System.getProperty("javax.net.ssl.trustStoreType", KeyStore.getDefaultType())); + String truststoreUrl = validateAndGetUrl(truststorePath); + + if (!isNullOrEmpty(truststoreUrl) || !isNullOrEmpty(keystoreUrl)) { + // Custom truststore or keystore provided - create validating SSLContext + SSLContext context = getValidatingSSLContext(truststoreUrl, truststoreType, truststorePassword, + keystoreUrl, keystoreType, keystorePassword); + sslSocketFactory = context.getSocketFactory(); + } + // else: No custom truststore/keystore - leave sslSocketFactory as null + // Jedis will use SSLContext.getDefault() with JVM's default truststore } } } diff --git a/driver/src/main/java/jdbc/properties/RedisDriverPropertyInfoHelper.java b/driver/src/main/java/jdbc/properties/RedisDriverPropertyInfoHelper.java index 945476a..3e60701 100644 --- a/driver/src/main/java/jdbc/properties/RedisDriverPropertyInfoHelper.java +++ b/driver/src/main/java/jdbc/properties/RedisDriverPropertyInfoHelper.java @@ -1,5 +1,6 @@ package jdbc.properties; +import java.security.KeyStore; import java.sql.DriverPropertyInfo; import java.util.ArrayList; @@ -17,6 +18,12 @@ public class RedisDriverPropertyInfoHelper { public static final String MAX_ATTEMPTS = "maxAttempts"; public static final String SSL = "ssl"; public static final String VERIFY_SERVER_CERTIFICATE = "verifyServerCertificate"; + public static final String TRUSTSTORE_PATH = "truststorePath"; + public static final String TRUSTSTORE_PASSWORD = "truststorePassword"; + public static final String TRUSTSTORE_TYPE = "truststoreType"; + public static final String KEYSTORE_PATH = "keystorePath"; + public static final String KEYSTORE_PASSWORD = "keystorePassword"; + public static final String KEYSTORE_TYPE = "keystoreType"; public static final String HOST_AND_PORT_MAPPING = "hostAndPortMapping"; public static final String HOST_AND_PORT_MAPPING_DEFAULT = null; @@ -43,6 +50,12 @@ public static DriverPropertyInfo[] getPropertyInfo() { addPropInfo(propInfos, SSL, String.valueOf(CONFIG.isSsl()), "Enable SSL.", booleanChoices); addPropInfo(propInfos, VERIFY_SERVER_CERTIFICATE, String.valueOf(CONFIG.isVerifyServerCertificate()), "Configure a connection that uses SSL but does not verify the identity of the server.", booleanChoices); + addPropInfo(propInfos, TRUSTSTORE_PATH, null, "Path to truststore file for server certificate validation."); + addPropInfo(propInfos, TRUSTSTORE_PASSWORD, null, "Password for truststore file."); + addPropInfo(propInfos, TRUSTSTORE_TYPE, KeyStore.getDefaultType(), "Truststore type (default: " + KeyStore.getDefaultType() + ")."); + addPropInfo(propInfos, KEYSTORE_PATH, null, "Path to keystore file for client certificate authentication."); + addPropInfo(propInfos, KEYSTORE_PASSWORD, null, "Password for keystore file."); + addPropInfo(propInfos, KEYSTORE_TYPE, KeyStore.getDefaultType(), "Keystore type (default: " + KeyStore.getDefaultType() + ")."); addPropInfo(propInfos, HOST_AND_PORT_MAPPING, HOST_AND_PORT_MAPPING_DEFAULT, "Host and port mapping."); addPropInfo(propInfos, VERIFY_CONNECTION_MODE, String.valueOf(VERIFY_CONNECTION_MODE_DEFAULT), "Verify that the mode specified for a connection in the URL prefix matches the server mode (standalone, cluster, sentinel).", booleanChoices); diff --git a/driver/src/main/java/jdbc/utils/SSLUtils.java b/driver/src/main/java/jdbc/utils/SSLUtils.java index 5b12b97..f633d06 100644 --- a/driver/src/main/java/jdbc/utils/SSLUtils.java +++ b/driver/src/main/java/jdbc/utils/SSLUtils.java @@ -15,62 +15,50 @@ public class SSLUtils { public static SSLContext getTrustEverybodySSLContext(String clientCertificateKeyStoreUrl, String clientCertificateKeyStoreType, String clientCertificateKeyStorePassword) throws SSLParamsException { - KeyManagerFactory kmf; + // Delegate to unified method with trust-all flag + TrustManager[] tms = new TrustManager[] { new MyTrustEverybodyManager() }; + // Load KeyManagers (for client certificate authentication) KeyManager[] kms = null; + if (!isNullOrEmpty(clientCertificateKeyStoreUrl)) { + kms = loadKeyManagers(clientCertificateKeyStoreUrl, clientCertificateKeyStoreType, clientCertificateKeyStorePassword); + } + // Create and initialize SSLContext try { - kmf = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm()); + SSLContext sslContext = SSLContext.getInstance("TLS"); + sslContext.init(kms, tms, null); + return sslContext; } catch (NoSuchAlgorithmException nsae) { - throw new SSLParamsException("Default algorithm definitions for TrustManager and/or KeyManager are invalid. Check java security properties file.", nsae); + throw new SSLParamsException("TLS is not a valid SSL protocol.", nsae); } + catch (KeyManagementException kme) { + throw new SSLParamsException("KeyManagementException: " + kme.getMessage(), kme); + } + } - if (!isNullOrEmpty(clientCertificateKeyStoreUrl)) { - InputStream ksIS = null; - try { - if (!isNullOrEmpty(clientCertificateKeyStoreType)) { - KeyStore clientKeyStore = KeyStore.getInstance(clientCertificateKeyStoreType); - URL ksURL = new URL(clientCertificateKeyStoreUrl); - char[] password = (clientCertificateKeyStorePassword == null) ? new char[0] : clientCertificateKeyStorePassword.toCharArray(); - ksIS = ksURL.openStream(); - clientKeyStore.load(ksIS, password); - kmf.init(clientKeyStore, password); - kms = kmf.getKeyManagers(); - } - } - catch (UnrecoverableKeyException uke) { - throw new SSLParamsException("Could not recover keys from client keystore. Check password?", uke); - } - catch (NoSuchAlgorithmException nsae) { - throw new SSLParamsException("Unsupported keystore algorithm [" + nsae.getMessage() + "]", nsae); - } - catch (KeyStoreException kse) { - throw new SSLParamsException("Could not create KeyStore instance [" + kse.getMessage() + "]", kse); - } - catch (CertificateException nsae) { - throw new SSLParamsException("Could not load client" + clientCertificateKeyStoreType + " keystore from " + clientCertificateKeyStoreUrl, nsae); - } - catch (MalformedURLException mue) { - throw new SSLParamsException(clientCertificateKeyStoreUrl + " does not appear to be a valid URL.", mue); - } - catch (IOException ioe) { - throw new SSLParamsException("Cannot open " + clientCertificateKeyStoreUrl + " [" + ioe.getMessage() + "]", ioe); - } - finally { - if (ksIS != null) { - try { - ksIS.close(); - } - catch (IOException e) { - // can't close input stream, but keystore can be properly initialized so we shouldn't throw this exception - } - } - } + public static SSLContext getValidatingSSLContext(String truststoreUrl, String truststoreType, String truststorePassword, + String keystoreUrl, String keystoreType, String keystorePassword) throws SSLParamsException + { + // Delegate to unified method with validation enabled + TrustManager[] tms; + if (!isNullOrEmpty(truststoreUrl)) { + // SECURE: Load truststore for server certificate validation + tms = loadTrustManagers(truststoreUrl, truststoreType, truststorePassword); + } else { + // No truststore provided - use default + tms = null; + } + // Load KeyManagers (for client certificate authentication) + KeyManager[] kms = null; + if (!isNullOrEmpty(keystoreUrl)) { + kms = loadKeyManagers(keystoreUrl, keystoreType, keystorePassword); } + // Create and initialize SSLContext try { SSLContext sslContext = SSLContext.getInstance("TLS"); - sslContext.init(kms, new TrustManager[]{new MyTrustEverybodyManager()}, null); + sslContext.init(kms, tms, null); return sslContext; } catch (NoSuchAlgorithmException nsae) { @@ -81,6 +69,94 @@ public static SSLContext getTrustEverybodySSLContext(String clientCertificateKey } } + private static TrustManager[] loadTrustManagers(String truststoreUrl, String truststoreType, String truststorePassword) + throws SSLParamsException + { + InputStream tsIS = null; + try { + TrustManagerFactory tmf = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm()); + // Use provided type or default to JKS if not specified + String storeType = isNullOrEmpty(truststoreType) ? KeyStore.getDefaultType() : truststoreType; + KeyStore trustStore = KeyStore.getInstance(storeType); + URL tsURL = new URL(truststoreUrl); + char[] password = (truststorePassword == null) ? new char[0] : truststorePassword.toCharArray(); + tsIS = tsURL.openStream(); + trustStore.load(tsIS, password); + tmf.init(trustStore); + return tmf.getTrustManagers(); + } + catch (NoSuchAlgorithmException nsae) { + throw new SSLParamsException("Unsupported truststore algorithm [" + nsae.getMessage() + "]", nsae); + } + catch (KeyStoreException kse) { + throw new SSLParamsException("Could not create TrustStore instance [" + kse.getMessage() + "]", kse); + } + catch (CertificateException ce) { + throw new SSLParamsException("Could not load truststore from " + truststoreUrl, ce); + } + catch (MalformedURLException mue) { + throw new SSLParamsException(truststoreUrl + " does not appear to be a valid URL.", mue); + } + catch (IOException ioe) { + throw new SSLParamsException("Cannot open " + truststoreUrl + " [" + ioe.getMessage() + "]", ioe); + } + finally { + if (tsIS != null) { + try { + tsIS.close(); + } + catch (IOException e) { + // can't close input stream, but trueststore can be properly initialized so we shouldn't throw this exception + } + } + } + } + + private static KeyManager[] loadKeyManagers(String keystoreUrl, String keystoreType, String keystorePassword) throws SSLParamsException + { + InputStream ksIS = null; + try { + KeyManagerFactory kmf = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm()); + // Use provided type or default to JKS if not specified + String storeType = isNullOrEmpty(keystoreType) ? KeyStore.getDefaultType() : keystoreType; + KeyStore keyStore = KeyStore.getInstance(storeType); + URL ksURL = new URL(keystoreUrl); + char[] password = (keystorePassword == null) ? new char[0] : keystorePassword.toCharArray(); + ksIS = ksURL.openStream(); + keyStore.load(ksIS, password); + kmf.init(keyStore, password); + return kmf.getKeyManagers(); + } + catch (UnrecoverableKeyException uke) { + throw new SSLParamsException("Could not recover keys from client keystore. Check password?", uke); + } + catch (NoSuchAlgorithmException nsae) { + throw new SSLParamsException("Unsupported keystore algorithm [" + nsae.getMessage() + "]", nsae); + } + catch (KeyStoreException kse) { + throw new SSLParamsException("Could not create KeyStore instance [" + kse.getMessage() + "]", kse); + } + catch (CertificateException ce) { + throw new SSLParamsException("Could not load keystore from " + keystoreUrl, ce); + } + catch (MalformedURLException mue) { + throw new SSLParamsException(keystoreUrl + " does not appear to be a valid URL.", mue); + } + catch (IOException ioe) { + throw new SSLParamsException("Cannot open " + keystoreUrl + " [" + ioe.getMessage() + "]", ioe); + } + finally { + if (ksIS != null) { + try { + ksIS.close(); + } + catch (IOException e) { + // can't close input stream, but keystore can be properly initialized so we shouldn't throw this exception + } + } + } + } + private static class MyTrustEverybodyManager implements X509TrustManager { public void checkClientTrusted(X509Certificate[] x509Certificates, String s) { From 9783712a2d4d8e4810065cec3f2738fe16a52c0c Mon Sep 17 00:00:00 2001 From: Dmytro Stonaiev Date: Wed, 15 Apr 2026 15:34:37 -0700 Subject: [PATCH 2/2] fix review comments --- README.md | 22 ++++++++++-- build.gradle | 3 ++ driver/src/main/java/jdbc/utils/SSLUtils.java | 2 +- driver/src/test/jdbc/client/RedisURITest.java | 33 ++++++++++++++++++ .../resources/jdbc/client/test-keystore.jks | Bin 0 -> 2700 bytes .../resources/jdbc/client/test-truststore.jks | Bin 0 -> 1238 bytes 6 files changed, 57 insertions(+), 3 deletions(-) create mode 100644 driver/src/test/resources/jdbc/client/test-keystore.jks create mode 100644 driver/src/test/resources/jdbc/client/test-truststore.jks diff --git a/README.md b/README.md index 9b69cdc..2a3fdc8 100644 --- a/README.md +++ b/README.md @@ -90,6 +90,12 @@ jdbc:redis:cluster://[[:]@][[:],[:], | clientName | String | null | | | ssl | Boolean | false | Enable SSL. | | verifyServerCertificate | Boolean | true | Configure a connection that uses SSL but does not verify the identity of the server. | +| truststorePath | String | null | Path to truststore file for server certificate validation. | +| truststorePassword | String | null | Password for truststore file. | +| truststoreType | String | JKS | Truststore type (JKS, PKCS12, etc.). | +| keystorePath | String | null | Path to keystore file for client certificate authentication. | +| keystorePassword | String | null | Password for keystore file. | +| keystoreType | String | JKS | Keystore type (JKS, PKCS12, etc.). | | hostAndPortMapping | Map | null | | | verifyConnectionMode | Boolean | true | Verify that the mode specified for a connection in the URL prefix matches the server mode (standalone, cluster, sentinel). | @@ -97,14 +103,26 @@ jdbc:redis:cluster://[[:]@][[:],[:], Set the property `ssl` to `true`. -Pass arguments for your keystore and trust store: +Configure keystore and truststore using connection properties or system properties (as fallback): + +``` +jdbc:redis://[[:]@][[:]][/]?ssl=true&truststorePath=/path/to/client.truststore&truststorePassword=password&=&=&...] +``` + +For client authentication (mutual TLS), also provide keystore: + +``` +jdbc:redis://[[:]@][[:]][/]?ssl=true&keystorePath=/path/to/client.keystore&keystorePassword=password&=&=&...] +``` + +System properties are used as fallback if connection properties are not provided: ``` -Djavax.net.ssl.trustStore=/path/to/client.truststore -Djavax.net.ssl.trustStorePassword=password123 -# If you're using client authentication: -Djavax.net.ssl.keyStore=/path/to/client.keystore -Djavax.net.ssl.keyStorePassword=password123 ``` + To disable server certificate verification set the property `verifyServerCertificate=false`. ### Port Forwarding diff --git a/build.gradle b/build.gradle index aca6f7a..5e40d6e 100644 --- a/build.gradle +++ b/build.gradle @@ -23,6 +23,9 @@ sourceSets { java { srcDirs = ['driver/src/test'] } + resources { + srcDirs = ['driver/src/test/resources'] + } } } diff --git a/driver/src/main/java/jdbc/utils/SSLUtils.java b/driver/src/main/java/jdbc/utils/SSLUtils.java index f633d06..1005b85 100644 --- a/driver/src/main/java/jdbc/utils/SSLUtils.java +++ b/driver/src/main/java/jdbc/utils/SSLUtils.java @@ -106,7 +106,7 @@ private static TrustManager[] loadTrustManagers(String truststoreUrl, String tru tsIS.close(); } catch (IOException e) { - // can't close input stream, but trueststore can be properly initialized so we shouldn't throw this exception + // can't close input stream, but truststore can be properly initialized so we shouldn't throw this exception } } } diff --git a/driver/src/test/jdbc/client/RedisURITest.java b/driver/src/test/jdbc/client/RedisURITest.java index f1d9fda..563dd9d 100644 --- a/driver/src/test/jdbc/client/RedisURITest.java +++ b/driver/src/test/jdbc/client/RedisURITest.java @@ -5,6 +5,10 @@ import org.junit.Test; import redis.clients.jedis.HostAndPort; +import java.net.URISyntaxException; +import java.net.URL; +import java.net.URLEncoder; +import java.nio.charset.StandardCharsets; import java.sql.SQLException; import java.util.Comparator; @@ -102,4 +106,33 @@ public void testClusterURI() throws SQLException { assertEquals(0, uri.getDatabase()); assertEquals(6, uri.getMaxAttempts()); } + + @Test + public void testSSLWithTruststoreParams() throws SQLException, URISyntaxException { + URL resource = RedisURITest.class.getResource("test-truststore.jks"); + String truststorePath = URLEncoder.encode(resource.toURI().getPath(), StandardCharsets.UTF_8); + RedisJedisURI uri = new RedisJedisURI("jdbc:redis://?ssl=true&truststorePath=" + truststorePath + "&truststorePassword=testpass&truststoreType=JKS", null); + assertTrue(uri.isSsl()); + assertNotNull(uri.getSslSocketFactory()); + } + + @Test + public void testSSLWithKeystoreParams() throws SQLException, URISyntaxException { + URL resource = RedisURITest.class.getResource("test-keystore.jks"); + String keystorePath = URLEncoder.encode(resource.toURI().getPath(), StandardCharsets.UTF_8); + RedisJedisURI uri = new RedisJedisURI("jdbc:redis://?ssl=true&keystorePath=" + keystorePath + "&keystorePassword=testpass&keystoreType=JKS", null); + assertTrue(uri.isSsl()); + assertNotNull(uri.getSslSocketFactory()); + } + + @Test + public void testSSLWithTruststoreAndKeystoreParams() throws SQLException, URISyntaxException { + URL truststoreResource = RedisURITest.class.getResource("test-truststore.jks"); + URL keystoreResource = RedisURITest.class.getResource("test-keystore.jks"); + String truststorePath = URLEncoder.encode(truststoreResource.toURI().getPath(), StandardCharsets.UTF_8); + String keystorePath = URLEncoder.encode(keystoreResource.toURI().getPath(), StandardCharsets.UTF_8); + RedisJedisURI uri = new RedisJedisURI("jdbc:redis://?ssl=true&truststorePath=" + truststorePath + "&truststorePassword=testpass&keystorePath=" + keystorePath + "&keystorePassword=testpass", null); + assertTrue(uri.isSsl()); + assertNotNull(uri.getSslSocketFactory()); + } } diff --git a/driver/src/test/resources/jdbc/client/test-keystore.jks b/driver/src/test/resources/jdbc/client/test-keystore.jks new file mode 100644 index 0000000000000000000000000000000000000000..e76ae38d8b32d0186f2c93790f74ba72d6e623f5 GIT binary patch literal 2700 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 XHM@BbEmH)@P(Y0MH0&1qD^mUfw?+J@ literal 0 HcmV?d00001 diff --git a/driver/src/test/resources/jdbc/client/test-truststore.jks b/driver/src/test/resources/jdbc/client/test-truststore.jks new file mode 100644 index 0000000000000000000000000000000000000000..fa2f0f1519486a725d48d3f11451beb432d558ad GIT binary patch literal 1238 zcmV;{1S$J4f&|h60Ru3C1bhYwDuzgg_YDCD0ic2eZ3Kb@X)uBWWiWySVFn2*hDe6@ z4FLxRpn?QaFoFb50s#Opf&@nf2`Yw2hW8Bt2LUi<1_>&LNQU+thDZTr0|Wso1Q1oS0-L*aWgBA$u9p1Mt@)m4h533-)<4PuCEGJm^J`Hrw~Vpa>LQ!IOGdOB4Ms?8a5#> z?ew--^QBj6pu^TgzLE{xPPes|Rk`ut%={mP@X-in?*bL0E+}uV<(wX7+9mY>vMW*8M0CxXUh!AMh2xFM7dc*=+ka8QdADe0LR|L?W^?DbuApF zHo1+&8&d^IU(1!?=I3*8`-A`$I*fNqDp2kGND%a?^b}sz^R09O;o8D9rQ7xaeVia6 z+Qg<6{y(kxw>9K}@V`Oub6TiRZqkCzsNT4Je-x_g1`vr-BkxMyu*|GM z?3yCTED8kc6NX4bA=*NQ)kaf&)+D>`0>zkpK7!beZkWqOK~(cW0}f^CmQRgp%DhHe z^n)aW#&U7-g#NRBi@OxKjz_M=y{N7tH0w|L?I7>>bu$j-%YKuTL(guek#$%g` z5TjMss^qbnW+Nxf7kup=S%FbMoj=R^bq@k%$}69x`21ju&K=#4)fWF}M*5m+0i-2~ z`-|mr5FRSntUl-pK?S0Aqp=1P9}l^w1lre{S{9J9?3S&zoho7zNC9O71OfpC00bb`8_cvNqhK!q z`eC=Nvv-dl0d;220}cRA1~F}oZ#ne@6#S9ECUEMxCUpopQIf#Z`326a76Jk%5Lr|` A9RL6T literal 0 HcmV?d00001