From 30907ffd8cdc78efb7a59e0425fe4e8e1b920648 Mon Sep 17 00:00:00 2001 From: jbertram Date: Thu, 11 Feb 2016 16:07:32 -0600 Subject: [PATCH] ARTEMIS-400 allow SSL store reload --- .../api/core/management/AcceptorControl.java | 6 + .../management/impl/AcceptorControlImpl.java | 11 ++ .../core/remoting/impl/invm/InVMAcceptor.java | 5 + .../remoting/impl/netty/NettyAcceptor.java | 156 +++++++++--------- .../artemis/spi/core/remoting/Acceptor.java | 6 + .../AcceptorControlUsingCoreTest.java | 10 ++ .../ssl/CoreClientOverOneWaySSLTest.java | 72 +++++++- .../server/impl/fake/FakeAcceptorFactory.java | 5 + .../other-client-side-truststore.jceks | Bin 0 -> 908 bytes .../other-client-side-truststore.jks | Bin 0 -> 975 bytes .../other-server-side-keystore.jceks | Bin 0 -> 1288 bytes .../resources/other-server-side-keystore.jks | Bin 0 -> 2264 bytes 12 files changed, 193 insertions(+), 78 deletions(-) create mode 100644 tests/unit-tests/src/test/resources/other-client-side-truststore.jceks create mode 100644 tests/unit-tests/src/test/resources/other-client-side-truststore.jks create mode 100644 tests/unit-tests/src/test/resources/other-server-side-keystore.jceks create mode 100644 tests/unit-tests/src/test/resources/other-server-side-keystore.jks diff --git a/artemis-core-client/src/main/java/org/apache/activemq/artemis/api/core/management/AcceptorControl.java b/artemis-core-client/src/main/java/org/apache/activemq/artemis/api/core/management/AcceptorControl.java index 97e49875cc..45df0dff94 100644 --- a/artemis-core-client/src/main/java/org/apache/activemq/artemis/api/core/management/AcceptorControl.java +++ b/artemis-core-client/src/main/java/org/apache/activemq/artemis/api/core/management/AcceptorControl.java @@ -41,4 +41,10 @@ public interface AcceptorControl extends ActiveMQComponentControl { */ @Attribute(desc = "parameters used to configure this acceptor") Map getParameters(); + + /** + * Re-create the acceptor with the existing configuration values. Useful, for example, for reloading key/trust + * stores on acceptors which support SSL. + */ + void reload(); } diff --git a/artemis-server/src/main/java/org/apache/activemq/artemis/core/management/impl/AcceptorControlImpl.java b/artemis-server/src/main/java/org/apache/activemq/artemis/core/management/impl/AcceptorControlImpl.java index ac6950d5c1..ae7660b667 100644 --- a/artemis-server/src/main/java/org/apache/activemq/artemis/core/management/impl/AcceptorControlImpl.java +++ b/artemis-server/src/main/java/org/apache/activemq/artemis/core/management/impl/AcceptorControlImpl.java @@ -83,6 +83,17 @@ public class AcceptorControlImpl extends AbstractControl implements AcceptorCont } } + @Override + public void reload() { + clearIO(); + try { + acceptor.reload(); + } + finally { + blockOnIO(); + } + } + @Override public boolean isStarted() { clearIO(); diff --git a/artemis-server/src/main/java/org/apache/activemq/artemis/core/remoting/impl/invm/InVMAcceptor.java b/artemis-server/src/main/java/org/apache/activemq/artemis/core/remoting/impl/invm/InVMAcceptor.java index 2967b2a3ef..190a5d1718 100644 --- a/artemis-server/src/main/java/org/apache/activemq/artemis/core/remoting/impl/invm/InVMAcceptor.java +++ b/artemis-server/src/main/java/org/apache/activemq/artemis/core/remoting/impl/invm/InVMAcceptor.java @@ -247,6 +247,11 @@ public final class InVMAcceptor extends AbstractAcceptor { return true; } + @Override + public void reload() { + throw new UnsupportedOperationException(); + } + @Override public void setDefaultActiveMQPrincipal(ActiveMQPrincipal defaultActiveMQPrincipal) { this.defaultActiveMQPrincipal = defaultActiveMQPrincipal; diff --git a/artemis-server/src/main/java/org/apache/activemq/artemis/core/remoting/impl/netty/NettyAcceptor.java b/artemis-server/src/main/java/org/apache/activemq/artemis/core/remoting/impl/netty/NettyAcceptor.java index 3da612f226..48922aa7b0 100644 --- a/artemis-server/src/main/java/org/apache/activemq/artemis/core/remoting/impl/netty/NettyAcceptor.java +++ b/artemis-server/src/main/java/org/apache/activemq/artemis/core/remoting/impl/netty/NettyAcceptor.java @@ -32,7 +32,6 @@ import java.util.concurrent.ConcurrentMap; import java.util.concurrent.ScheduledExecutorService; import java.util.concurrent.ScheduledFuture; import java.util.concurrent.TimeUnit; -import java.util.concurrent.atomic.AtomicBoolean; import io.netty.bootstrap.ServerBootstrap; import io.netty.channel.Channel; @@ -121,7 +120,8 @@ public class NettyAcceptor extends AbstractAcceptor { private final String keyStoreProvider; - private final String keyStorePath; + // non-final for testing purposes + private String keyStorePath; private final String keyStorePassword; @@ -282,87 +282,13 @@ public class NettyAcceptor extends AbstractAcceptor { bootstrap = new ServerBootstrap(); bootstrap.group(eventLoopGroup); bootstrap.channel(channelClazz); - final SSLContext context; - if (sslEnabled) { - try { - if (keyStorePath == null && TransportConstants.DEFAULT_TRUSTSTORE_PROVIDER.equals(keyStoreProvider)) - throw new IllegalArgumentException("If \"" + TransportConstants.SSL_ENABLED_PROP_NAME + - "\" is true then \"" + TransportConstants.KEYSTORE_PATH_PROP_NAME + "\" must be non-null " + - "unless an alternative \"" + TransportConstants.KEYSTORE_PROVIDER_PROP_NAME + "\" has been specified."); - context = SSLSupport.createContext(keyStoreProvider, keyStorePath, keyStorePassword, trustStoreProvider, trustStorePath, trustStorePassword); - } - catch (Exception e) { - IllegalStateException ise = new IllegalStateException("Unable to create NettyAcceptor for " + host + - ":" + port); - ise.initCause(e); - throw ise; - } - } - else { - context = null; // Unused - } - - final AtomicBoolean warningPrinted = new AtomicBoolean(false); ChannelInitializer factory = new ChannelInitializer() { @Override public void initChannel(Channel channel) throws Exception { ChannelPipeline pipeline = channel.pipeline(); if (sslEnabled) { - SSLEngine engine = context.createSSLEngine(); - - engine.setUseClientMode(false); - - if (needClientAuth) - engine.setNeedClientAuth(true); - - // setting the enabled cipher suites resets the enabled protocols so we need - // to save the enabled protocols so that after the customer cipher suite is enabled - // we can reset the enabled protocols if a customer protocol isn't specified - String[] originalProtocols = engine.getEnabledProtocols(); - - if (enabledCipherSuites != null) { - try { - engine.setEnabledCipherSuites(SSLSupport.parseCommaSeparatedListIntoArray(enabledCipherSuites)); - } - catch (IllegalArgumentException e) { - ActiveMQServerLogger.LOGGER.invalidCipherSuite(SSLSupport.parseArrayIntoCommandSeparatedList(engine.getSupportedCipherSuites())); - throw e; - } - } - - if (enabledProtocols != null) { - try { - engine.setEnabledProtocols(SSLSupport.parseCommaSeparatedListIntoArray(enabledProtocols)); - } - catch (IllegalArgumentException e) { - ActiveMQServerLogger.LOGGER.invalidProtocol(SSLSupport.parseArrayIntoCommandSeparatedList(engine.getSupportedProtocols())); - throw e; - } - } - else { - engine.setEnabledProtocols(originalProtocols); - } - - // Strip "SSLv3" from the current enabled protocols to address the POODLE exploit. - // This recommendation came from http://www.oracle.com/technetwork/java/javase/documentation/cve-2014-3566-2342133.html - String[] protocols = engine.getEnabledProtocols(); - Set set = new HashSet<>(); - for (String s : protocols) { - if (s.equals("SSLv3") || s.equals("SSLv2Hello")) { - if (!warningPrinted.get()) { - ActiveMQServerLogger.LOGGER.disallowedProtocol(s); - } - continue; - } - set.add(s); - } - warningPrinted.set(true); - engine.setEnabledProtocols(set.toArray(new String[set.size()])); - - SslHandler handler = new SslHandler(engine); - - pipeline.addLast("ssl", handler); + pipeline.addLast("ssl", getSslHandler()); } pipeline.addLast(protocolHandler.getProtocolDecoder()); } @@ -421,6 +347,11 @@ public class NettyAcceptor extends AbstractAcceptor { return name; } + // only for testing purposes + public void setKeyStorePath(String keyStorePath) { + this.keyStorePath = keyStorePath; + } + /** * Transfers the Netty channel that has been created outside of this NettyAcceptor * to control it and configure it according to this NettyAcceptor setting. @@ -434,6 +365,77 @@ public class NettyAcceptor extends AbstractAcceptor { channel.pipeline().addLast(protocolHandler.getProtocolDecoder()); } + public void reload() { + serverChannelGroup.disconnect(); + serverChannelGroup.clear(); + startServerChannels(); + } + + public synchronized SslHandler getSslHandler() throws Exception { + final SSLContext context; + try { + if (keyStorePath == null && TransportConstants.DEFAULT_TRUSTSTORE_PROVIDER.equals(keyStoreProvider)) + throw new IllegalArgumentException("If \"" + TransportConstants.SSL_ENABLED_PROP_NAME + + "\" is true then \"" + TransportConstants.KEYSTORE_PATH_PROP_NAME + "\" must be non-null " + + "unless an alternative \"" + TransportConstants.KEYSTORE_PROVIDER_PROP_NAME + "\" has been specified."); + context = SSLSupport.createContext(keyStoreProvider, keyStorePath, keyStorePassword, trustStoreProvider, trustStorePath, trustStorePassword); + } + catch (Exception e) { + IllegalStateException ise = new IllegalStateException("Unable to create NettyAcceptor for " + host + ":" + port); + ise.initCause(e); + throw ise; + } + SSLEngine engine = context.createSSLEngine(); + + engine.setUseClientMode(false); + + if (needClientAuth) + engine.setNeedClientAuth(true); + + // setting the enabled cipher suites resets the enabled protocols so we need + // to save the enabled protocols so that after the customer cipher suite is enabled + // we can reset the enabled protocols if a customer protocol isn't specified + String[] originalProtocols = engine.getEnabledProtocols(); + + if (enabledCipherSuites != null) { + try { + engine.setEnabledCipherSuites(SSLSupport.parseCommaSeparatedListIntoArray(enabledCipherSuites)); + } + catch (IllegalArgumentException e) { + ActiveMQServerLogger.LOGGER.invalidCipherSuite(SSLSupport.parseArrayIntoCommandSeparatedList(engine.getSupportedCipherSuites())); + throw e; + } + } + + if (enabledProtocols != null) { + try { + engine.setEnabledProtocols(SSLSupport.parseCommaSeparatedListIntoArray(enabledProtocols)); + } + catch (IllegalArgumentException e) { + ActiveMQServerLogger.LOGGER.invalidProtocol(SSLSupport.parseArrayIntoCommandSeparatedList(engine.getSupportedProtocols())); + throw e; + } + } + else { + engine.setEnabledProtocols(originalProtocols); + } + + // Strip "SSLv3" from the current enabled protocols to address the POODLE exploit. + // This recommendation came from http://www.oracle.com/technetwork/java/javase/documentation/cve-2014-3566-2342133.html + String[] protocols = engine.getEnabledProtocols(); + Set set = new HashSet<>(); + for (String s : protocols) { + if (s.equalsIgnoreCase("SSLv3") || s.equals("SSLv2Hello")) { + ActiveMQServerLogger.LOGGER.disallowedProtocol(s); + continue; + } + set.add(s); + } + + engine.setEnabledProtocols(set.toArray(new String[set.size()])); + return new SslHandler(engine); + } + private void startServerChannels() { String[] hosts = TransportConfiguration.splitHosts(host); for (String h : hosts) { diff --git a/artemis-server/src/main/java/org/apache/activemq/artemis/spi/core/remoting/Acceptor.java b/artemis-server/src/main/java/org/apache/activemq/artemis/spi/core/remoting/Acceptor.java index b4c195282a..c0da381d46 100644 --- a/artemis-server/src/main/java/org/apache/activemq/artemis/spi/core/remoting/Acceptor.java +++ b/artemis-server/src/main/java/org/apache/activemq/artemis/spi/core/remoting/Acceptor.java @@ -70,4 +70,10 @@ public interface Acceptor extends ActiveMQComponent { * @throws java.lang.IllegalStateException if false @setDefaultActiveMQPrincipal */ boolean isUnsecurable(); + + /** + * Re-create the acceptor with the existing configuration values. Useful, for example, for reloading key/trust + * stores on acceptors which support SSL. + */ + void reload(); } diff --git a/tests/integration-tests/src/test/java/org/apache/activemq/artemis/tests/integration/management/AcceptorControlUsingCoreTest.java b/tests/integration-tests/src/test/java/org/apache/activemq/artemis/tests/integration/management/AcceptorControlUsingCoreTest.java index d5c7b65e94..8039e98bec 100644 --- a/tests/integration-tests/src/test/java/org/apache/activemq/artemis/tests/integration/management/AcceptorControlUsingCoreTest.java +++ b/tests/integration-tests/src/test/java/org/apache/activemq/artemis/tests/integration/management/AcceptorControlUsingCoreTest.java @@ -68,6 +68,16 @@ public class AcceptorControlUsingCoreTest extends AcceptorControlTest { return (Map) proxy.retrieveAttributeValue("parameters"); } + @Override + public void reload() { + try { + proxy.invokeOperation("reload"); + } + catch (Exception e) { + e.printStackTrace(); + } + } + @Override public boolean isStarted() { return (Boolean) proxy.retrieveAttributeValue("started"); diff --git a/tests/integration-tests/src/test/java/org/apache/activemq/artemis/tests/integration/ssl/CoreClientOverOneWaySSLTest.java b/tests/integration-tests/src/test/java/org/apache/activemq/artemis/tests/integration/ssl/CoreClientOverOneWaySSLTest.java index f23e58006d..50ac9980ca 100644 --- a/tests/integration-tests/src/test/java/org/apache/activemq/artemis/tests/integration/ssl/CoreClientOverOneWaySSLTest.java +++ b/tests/integration-tests/src/test/java/org/apache/activemq/artemis/tests/integration/ssl/CoreClientOverOneWaySSLTest.java @@ -36,6 +36,7 @@ import org.apache.activemq.artemis.api.core.client.ClientSession; import org.apache.activemq.artemis.api.core.client.ClientSessionFactory; import org.apache.activemq.artemis.api.core.client.ActiveMQClient; import org.apache.activemq.artemis.api.core.client.ServerLocator; +import org.apache.activemq.artemis.core.remoting.impl.netty.NettyAcceptor; import org.apache.activemq.artemis.tests.integration.IntegrationTestLogger; import org.apache.activemq.artemis.tests.util.ActiveMQTestBase; import org.apache.activemq.artemis.utils.RandomUtil; @@ -73,10 +74,18 @@ public class CoreClientOverOneWaySSLTest extends ActiveMQTestBase { * keytool -export -keystore server-side-keystore.jks -file activemq-jks.cer -storepass secureexample * keytool -import -keystore client-side-truststore.jks -file activemq-jks.cer -storepass secureexample -keypass secureexample -noprompt * + * keytool -genkey -keystore other-server-side-keystore.jks -storepass secureexample -keypass secureexample -dname "CN=Other ActiveMQ Artemis Server, OU=Artemis, O=ActiveMQ, L=AMQ, S=AMQ, C=AMQ" -keyalg RSA + * keytool -export -keystore other-server-side-keystore.jks -file activemq-jks.cer -storepass secureexample + * keytool -import -keystore other-client-side-truststore.jks -file activemq-jks.cer -storepass secureexample -keypass secureexample -noprompt + * * Commands to create the JCEKS artifacts: * keytool -genkey -keystore server-side-keystore.jceks -storetype JCEKS -storepass secureexample -keypass secureexample -dname "CN=ActiveMQ Artemis Server, OU=Artemis, O=ActiveMQ, L=AMQ, S=AMQ, C=AMQ" * keytool -export -keystore server-side-keystore.jceks -file activemq-jceks.cer -storetype jceks -storepass secureexample * keytool -import -keystore client-side-truststore.jceks -storetype JCEKS -file activemq-jceks.cer -storepass secureexample -keypass secureexample -noprompt + * + * keytool -genkey -keystore other-server-side-keystore.jceks -storetype JCEKS -storepass secureexample -keypass secureexample -dname "CN=Other ActiveMQ Artemis Server, OU=Artemis, O=ActiveMQ, L=AMQ, S=AMQ, C=AMQ" + * keytool -export -keystore other-server-side-keystore.jceks -file activemq-jceks.cer -storetype jceks -storepass secureexample + * keytool -import -keystore other-client-side-truststore.jceks -storetype JCEKS -file activemq-jceks.cer -storepass secureexample -keypass secureexample -noprompt */ private String storeType; private String SERVER_SIDE_KEYSTORE; @@ -114,6 +123,67 @@ public class CoreClientOverOneWaySSLTest extends ActiveMQTestBase { Assert.assertEquals(text, m.getBodyBuffer().readString()); } + @Test + public void testOneWaySSLReloaded() throws Exception { + createCustomSslServer(); + server.createQueue(CoreClientOverOneWaySSLTest.QUEUE, CoreClientOverOneWaySSLTest.QUEUE, null, false, false); + String text = RandomUtil.randomString(); + + // create a valid SSL connection and keep it for use later + tc.getParams().put(TransportConstants.SSL_ENABLED_PROP_NAME, true); + tc.getParams().put(TransportConstants.TRUSTSTORE_PROVIDER_PROP_NAME, storeType); + tc.getParams().put(TransportConstants.TRUSTSTORE_PATH_PROP_NAME, CLIENT_SIDE_TRUSTSTORE); + tc.getParams().put(TransportConstants.TRUSTSTORE_PASSWORD_PROP_NAME, PASSWORD); + + ServerLocator existingLocator = addServerLocator(ActiveMQClient.createServerLocatorWithoutHA(tc)); + existingLocator.setCallTimeout(3000); + ClientSessionFactory existingSessionFactory = addSessionFactory(createSessionFactory(existingLocator)); + ClientSession existingSession = addClientSession(existingSessionFactory.createSession(false, true, true)); + ClientConsumer existingConsumer = addClientConsumer(existingSession.createConsumer(CoreClientOverOneWaySSLTest.QUEUE)); + + // create an invalid SSL connection + tc.getParams().put(TransportConstants.SSL_ENABLED_PROP_NAME, true); + tc.getParams().put(TransportConstants.TRUSTSTORE_PROVIDER_PROP_NAME, storeType); + tc.getParams().put(TransportConstants.TRUSTSTORE_PATH_PROP_NAME, "other-client-side-truststore." + storeType.toLowerCase()); + tc.getParams().put(TransportConstants.TRUSTSTORE_PASSWORD_PROP_NAME, PASSWORD); + + ServerLocator locator = addServerLocator(ActiveMQClient.createServerLocatorWithoutHA(tc)).setCallTimeout(3000); + try { + addSessionFactory(createSessionFactory(locator)); + fail("Creating session here should fail due to SSL handshake problems."); + } + catch (Exception e) { + // ignore + } + + // reload the acceptor to reload the SSL stores + NettyAcceptor acceptor = (NettyAcceptor) server.getRemotingService().getAcceptor("nettySSL"); + acceptor.setKeyStorePath("other-server-side-keystore." + storeType.toLowerCase()); + acceptor.reload(); + + // create a session with the locator which failed previously proving that the SSL stores have been reloaded + ClientSessionFactory sf = addSessionFactory(createSessionFactory(locator)); + ClientSession session = addClientSession(sf.createSession(false, true, true)); + ClientProducer producer = addClientProducer(session.createProducer(CoreClientOverOneWaySSLTest.QUEUE)); + + ClientMessage message = createTextMessage(session, text); + producer.send(message); + producer.send(message); + + ClientConsumer consumer = addClientConsumer(session.createConsumer(CoreClientOverOneWaySSLTest.QUEUE)); + session.start(); + Message m = consumer.receive(1000); + Assert.assertNotNull(m); + Assert.assertEquals(text, m.getBodyBuffer().readString()); + consumer.close(); + + // use the existing connection to prove it wasn't lost when the acceptor was reloaded + existingSession.start(); + m = existingConsumer.receive(1000); + Assert.assertNotNull(m); + Assert.assertEquals(text, m.getBodyBuffer().readString()); + } + @Test public void testOneWaySSLWithBadClientCipherSuite() throws Exception { createCustomSslServer(); @@ -533,7 +603,7 @@ public class CoreClientOverOneWaySSLTest extends ActiveMQTestBase { params.put(TransportConstants.ENABLED_PROTOCOLS_PROP_NAME, protocols); } - ConfigurationImpl config = createBasicConfig().addAcceptorConfiguration(new TransportConfiguration(NETTY_ACCEPTOR_FACTORY, params)); + ConfigurationImpl config = createBasicConfig().addAcceptorConfiguration(new TransportConfiguration(NETTY_ACCEPTOR_FACTORY, params, "nettySSL")); server = createServer(false, config); server.start(); waitForServerToStart(server); diff --git a/tests/unit-tests/src/test/java/org/apache/activemq/artemis/tests/unit/core/remoting/server/impl/fake/FakeAcceptorFactory.java b/tests/unit-tests/src/test/java/org/apache/activemq/artemis/tests/unit/core/remoting/server/impl/fake/FakeAcceptorFactory.java index 1c15c692d3..b2305ec1f8 100644 --- a/tests/unit-tests/src/test/java/org/apache/activemq/artemis/tests/unit/core/remoting/server/impl/fake/FakeAcceptorFactory.java +++ b/tests/unit-tests/src/test/java/org/apache/activemq/artemis/tests/unit/core/remoting/server/impl/fake/FakeAcceptorFactory.java @@ -89,6 +89,11 @@ public class FakeAcceptorFactory implements AcceptorFactory { return false; } + @Override + public void reload() { + + } + @Override public void start() throws Exception { started = true; diff --git a/tests/unit-tests/src/test/resources/other-client-side-truststore.jceks b/tests/unit-tests/src/test/resources/other-client-side-truststore.jceks new file mode 100644 index 0000000000000000000000000000000000000000..91c4caf234eb3ed353898e8181a9e3039a9359b4 GIT binary patch literal 908 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 literal 0 HcmV?d00001 diff --git a/tests/unit-tests/src/test/resources/other-server-side-keystore.jks b/tests/unit-tests/src/test/resources/other-server-side-keystore.jks new file mode 100644 index 0000000000000000000000000000000000000000..8e8d1c9f62e42f0ac833b87d8028597d222aad92 GIT binary patch literal 2264 zcmc(g=U0;n7RK|YPy(TbqK43vrIUmjdNFu=0V79{h!KZm zKg3hQ1c6`xh7R4w@UX!Z*Z?RX59S8|7zj#-E@?mV32g+2m7HRR^ij6&2^%DyvQ``s zPF)Q?nZ~vomBy;;;niAp%ZyYZz0cPj&IDq~Vzce>6dhJa#c{h_=Z{XU!;yTDmOkY> zMoERZC{ei$gH=vHQ-cE4v0Id`r_b#j?E;cEg?zx}6_#=Hx#yoV>-@>hU*tNYN@J>` z)59U0^Z6Z)e0@L<36=9AM-!$ed_0tu*T8FNKGdAomt-njHH*N<>ZWb%d zl&eiu^($@E*uq!5OPC(J8Qpt`WR|l{GhUaWAD*Wuno^ysFj`|cM-6R(T{Jubn-VqV z<*A?73I3)3=$YsYNyu$|7D;^4lW@>~WJVNZYOQjop>k{B?wo;sn*m!`v(^R9)VTaT z2Uf4^jt@Cu;`}iC+kOOsEMd%_ArBq!qZHOAqZ1TQNY8pob^N&NRa6MR{&TMIp~JXP zAljsDfJR^frd-mD@^$iF7RhB}51EQifk>{Q^V1K~#GC4~7A@3I>n==mYMpwuDxz)t+C$4_7%x_!|3XzL zdH{DncL{9|k95=azXeh{;9?fb^)br62m{hGD?^~y2BbH5ZilfL84#$z9bU`8nw=4NUl!ihTdjZXgQcg@{_~i z5xd#n^9-WVL+mQZLLQkktLY~AWKpkQ?0f5_6^0S`xYU?61|CZGFO{|sG7hiOQKw0V znDrttRDWAeYWKrxZ+lqjRcBQ-QtIFE6*mqsF03q85xNdmxMYqKF||aMFd4L z)@XTDdPMnjr`lB+kI?9sAPS%m*O=L@8Ea9^Q6DHMn=Jgt7GsW5F{Bxhup@{!J&B& zFaQQatz=80F$gwJ`FKM74gvtUU?5C5mKy_SgPeh~2||pl?fxc?{~`9jh#$lIhY~Kx zVdNbW5Jo(d^Btbs!xWbwyHQXGksJ_=mBAeUBLfi>J00Rr43hX;D)AqIgaa`sj2MJL ziy*KX7#tRh!>DShVO{@3_5b4kuP{0w^-q%yUkHQ_@PaUO01l=D0O;4~i>2;KmFt{E zPGm=G_z4AJ?LLNwg89c@nzye=YmaOj{4Sb8eRmLd`KhQsG9pXQ0T*}{IMAzqvn_L< zk0GEm@4|aG+RigIsAbIkz&9lQjKJwTaXR=7LWjAF9ocg|N7?e&Y5bLIO~K`FuuAkP z;cL5$o5{k(Nt_OvYq`r&+a=c#kO=`TL}51|-gBkQv%>RVSd~XrSp?=uf9bD}fo*x) zRf|ADlDS5bZj6+qj_jQjYOKNTHGt1}g}|ff`~x>NZ|Mi*Y9|D;<9_L>jcB?rS?Rv} zIR6}n(RwL0+HiWG?L1Ve-1BC>XtzKC76KnB16#f}DEuH{Nd!Xx5Kt(I5yyxfHd>4a zDhx%Q0ZB;_6~`}gNh=1VZe}_YsRZ~xO+1{mLlXgz3qGf``DGi8+WNVu*TyjrE$8ur z)Dg3+Jsdne6IR9_as5^k6E%>V=Thd+d8ec%CoQ;{^4ZHHjZK}k)EizM!QZ&s_X*dzppJN~bq@XX+ryKJPpQ*`?rTNaM{YQ}={&HUG3&YHXO&1W{B7`_pqBqK z+9>4btR|1|lrHk~7?CF{e$=gz=-fU>=(8M9Le74R4ZWEXpnJI2i1I0wc_HrO8nYL! z`q