From: xuegao Date: Thu, 28 Nov 2019 14:13:18 +0000 (+0100) Subject: Merge ssl password X-Git-Tag: 5.0.0~92 X-Git-Url: https://gerrit.onap.org/r/gitweb?p=clamp.git;a=commitdiff_plain;h=1ebfe6b467e5a6a42c756f225397da76f9e3dfc2 Merge ssl password Use the aaf encrypted ssl password fot server.ssl parameters Issue-ID: CLAMP-339 Change-Id: I8869bb527f2851c1d298cd03e45327791a8acfab Signed-off-by: xuegao --- diff --git a/src/main/java/org/onap/clamp/clds/Application.java b/src/main/java/org/onap/clamp/clds/Application.java index efc4b128..e41140f5 100644 --- a/src/main/java/org/onap/clamp/clds/Application.java +++ b/src/main/java/org/onap/clamp/clds/Application.java @@ -29,6 +29,7 @@ import com.att.eelf.configuration.EELFLogger; import com.att.eelf.configuration.EELFManager; import java.io.IOException; +import java.io.InputStream; import java.security.KeyStore; import java.security.KeyStoreException; import java.security.NoSuchAlgorithmException; @@ -39,6 +40,7 @@ import java.util.Enumeration; import org.apache.catalina.connector.Connector; import org.onap.clamp.clds.util.ClampVersioning; import org.onap.clamp.clds.util.ResourceFileUtil; +import org.onap.clamp.util.PassDecoder; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Value; import org.springframework.boot.SpringApplication; @@ -135,6 +137,8 @@ public class Application extends SpringBootServletInitializer { return tomcat; } + + private Connector createRedirectConnector(int redirectSecuredPort) { if (redirectSecuredPort <= 0) { eelfLogger.warn("HTTP port redirection to HTTPS is disabled because the HTTPS port is 0 (random port) or -1" @@ -155,10 +159,12 @@ public class Application extends SpringBootServletInitializer { if (env.getProperty("server.ssl.key-store") != null) { KeyStore keystore = KeyStore.getInstance(env.getProperty("server.ssl.key-store-type")); - keystore.load( - ResourceFileUtil.getResourceAsStream( - env.getProperty("server.ssl.key-store").replaceAll("classpath:", "")), - env.getProperty("server.ssl.key-store-password").toCharArray()); + String password = PassDecoder.decode(env.getProperty("server.ssl.key-store-password"), + env.getProperty("clamp.config.keyFile")); + String keyStore = env.getProperty("server.ssl.key-store"); + InputStream is = ResourceFileUtil.getResourceAsStream(keyStore.replaceAll("classpath:", "")); + keystore.load(is, password.toCharArray()); + Enumeration aliases = keystore.aliases(); while (aliases.hasMoreElements()) { String alias = aliases.nextElement(); diff --git a/src/main/java/org/onap/clamp/clds/config/CamelConfiguration.java b/src/main/java/org/onap/clamp/clds/config/CamelConfiguration.java index 271dc84f..949ff1eb 100644 --- a/src/main/java/org/onap/clamp/clds/config/CamelConfiguration.java +++ b/src/main/java/org/onap/clamp/clds/config/CamelConfiguration.java @@ -48,6 +48,7 @@ import org.apache.http.conn.ssl.SSLSocketFactory; import org.apache.http.impl.client.HttpClientBuilder; import org.apache.http.impl.conn.BasicHttpClientConnectionManager; import org.onap.clamp.clds.util.ClampVersioning; +import org.onap.clamp.util.PassDecoder; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.core.env.Environment; import org.springframework.stereotype.Component; @@ -61,18 +62,24 @@ public class CamelConfiguration extends RouteBuilder { @Autowired private Environment env; - private void configureDefaultSslProperties() { + private void configureDefaultSslProperties() throws IOException { if (env.getProperty("server.ssl.trust-store") != null) { URL storeResource = Thread.currentThread().getContextClassLoader() .getResource(env.getProperty("server.ssl.trust-store").replaceAll("classpath:", "")); System.setProperty("javax.net.ssl.trustStore", storeResource.getPath()); - System.setProperty("javax.net.ssl.trustStorePassword", env.getProperty("server.ssl.trust-store-password")); + String keyFile = env.getProperty("clamp.config.keyFile"); + String trustStorePass = PassDecoder.decode(env.getProperty("server.ssl.trust-store-password"), + keyFile); + System.setProperty("javax.net.ssl.trustStorePassword", trustStorePass); System.setProperty("javax.net.ssl.trustStoreType", "jks"); System.setProperty("ssl.TrustManagerFactory.algorithm", "PKIX"); storeResource = Thread.currentThread().getContextClassLoader() .getResource(env.getProperty("server.ssl.key-store").replaceAll("classpath:", "")); System.setProperty("javax.net.ssl.keyStore", storeResource.getPath()); - System.setProperty("javax.net.ssl.keyStorePassword", env.getProperty("server.ssl.key-store-password")); + + String keyStorePass = PassDecoder.decode(env.getProperty("server.ssl.key-store-password"), + keyFile); + System.setProperty("javax.net.ssl.keyStorePassword", keyStorePass); System.setProperty("javax.net.ssl.keyStoreType", env.getProperty("server.ssl.key-store-type")); } } @@ -81,10 +88,12 @@ public class CamelConfiguration extends RouteBuilder { throws KeyStoreException, NoSuchAlgorithmException, KeyManagementException, CertificateException, IOException { if (env.getProperty("server.ssl.trust-store") != null) { KeyStore truststore = KeyStore.getInstance("JKS"); + String keyFile = env.getProperty("clamp.config.keyFile"); + String password = PassDecoder.decode(env.getProperty("server.ssl.trust-store-password"), keyFile); truststore.load( Thread.currentThread().getContextClassLoader() .getResourceAsStream(env.getProperty("server.ssl.trust-store").replaceAll("classpath:", "")), - env.getProperty("server.ssl.trust-store-password").toCharArray()); + password.toCharArray()); TrustManagerFactory trustFactory = TrustManagerFactory.getInstance("PKIX"); trustFactory.init(truststore); diff --git a/src/main/java/org/onap/clamp/clds/config/SslConfig.java b/src/main/java/org/onap/clamp/clds/config/SslConfig.java new file mode 100644 index 00000000..7c7433e9 --- /dev/null +++ b/src/main/java/org/onap/clamp/clds/config/SslConfig.java @@ -0,0 +1,97 @@ +/*- + * ============LICENSE_START======================================================= + * ONAP CLAMP + * ================================================================================ + * Copyright (C) 2019 AT&T Intellectual Property. All rights + * reserved. + * ================================================================================ + * 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. + * ============LICENSE_END============================================ + * =================================================================== + * + */ + +package org.onap.clamp.clds.config; + +import java.io.IOException; +import java.io.InputStream; +import java.security.KeyStore; +import java.security.KeyStoreException; +import java.security.NoSuchAlgorithmException; +import java.security.cert.CertificateException; + +import org.onap.clamp.clds.util.ResourceFileUtil; +import org.onap.clamp.util.PassDecoder; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.autoconfigure.web.ServerProperties; +import org.springframework.boot.web.embedded.tomcat.TomcatServletWebServerFactory; +import org.springframework.boot.web.server.Ssl; +import org.springframework.boot.web.server.SslStoreProvider; +import org.springframework.boot.web.server.WebServerFactoryCustomizer; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.Profile; +import org.springframework.core.env.Environment; +import org.springframework.core.io.ResourceLoader; + +@Configuration +@Profile("clamp-ssl-config") +public class SslConfig { + @Autowired + private Environment env; + + @Bean + WebServerFactoryCustomizer tomcatCustomizer(ServerProperties serverProperties, + ResourceLoader resourceLoader) { + return (tomcat) -> tomcat.setSslStoreProvider(new SslStoreProvider() { + @Override + public KeyStore getKeyStore() throws KeyStoreException, + NoSuchAlgorithmException, CertificateException, IOException { + KeyStore keystore = KeyStore.getInstance(env.getProperty("server.ssl.key-store-type")); + String password = PassDecoder.decode(env.getProperty("server.ssl.key-store-password"), + env.getProperty("clamp.config.keyFile")); + String keyStore = env.getProperty("server.ssl.key-store"); + InputStream is = ResourceFileUtil.getResourceAsStream(keyStore.replaceAll("classpath:", "")); + keystore.load(is, password.toCharArray()); + return keystore; + } + + @Override + public KeyStore getTrustStore() throws KeyStoreException, + NoSuchAlgorithmException, CertificateException, IOException { + KeyStore truststore = KeyStore.getInstance("JKS"); + String password = PassDecoder.decode(env.getProperty("server.ssl.trust-store-password"), + env.getProperty("clamp.config.keyFile")); + truststore.load( + Thread.currentThread().getContextClassLoader() + .getResourceAsStream(env.getProperty("server.ssl.trust-store") + .replaceAll("classpath:", "")), + password.toCharArray()); + return truststore; + } + }); + } + + @Bean + WebServerFactoryCustomizer tomcatSslCustomizer(ServerProperties serverProperties, + ResourceLoader resourceLoader) { + return (tomcat) -> tomcat.setSsl(new Ssl() { + @Override + public String getKeyPassword() { + String password = PassDecoder.decode(env.getProperty("server.ssl.key-password"), + env.getProperty("clamp.config.keyFile")); + return password; + } + }); + } +} \ No newline at end of file diff --git a/src/main/java/org/onap/clamp/clds/filter/ClampCadiFilter.java b/src/main/java/org/onap/clamp/clds/filter/ClampCadiFilter.java index 68544de6..9e04bd08 100644 --- a/src/main/java/org/onap/clamp/clds/filter/ClampCadiFilter.java +++ b/src/main/java/org/onap/clamp/clds/filter/ClampCadiFilter.java @@ -60,19 +60,19 @@ public class ClampCadiFilter extends CadiFilter { @Value("${server.ssl.key-store:#{null}}") private String keyStore; - @Value("${clamp.config.cadi.cadiKeystorePassword:#{null}}") + @Value("${server.ssl.key-store-password:#{null}}") private String keyStorePass; @Value("${server.ssl.trust-store:#{null}}") private String trustStore; - @Value("${clamp.config.cadi.cadiTruststorePassword:#{null}}") + @Value("${server.ssl.trust-store-password:#{null}}") private String trustStorePass; @Value("${server.ssl.key-alias:clamp@clamp.onap.org}") private String alias; - @Value("${clamp.config.cadi.keyFile:#{null}}") + @Value("${clamp.config.keyFile:#{null}}") private String keyFile; @Value("${clamp.config.cadi.cadiLoglevel:#{null}}") @@ -152,7 +152,8 @@ public class ClampCadiFilter extends CadiFilter { .generateCertificate(new ByteArrayInputStream( URLDecoder.decode(certHeader, StandardCharsets.UTF_8.toString()).getBytes())); X509Certificate caCert = (X509Certificate) certificateFactory - .generateCertificate(new ByteArrayInputStream(ResourceFileUtil.getResourceAsString("clds/aaf/ssl/ca-certs.pem").getBytes())); + .generateCertificate(new ByteArrayInputStream( + ResourceFileUtil.getResourceAsString("clds/aaf/ssl/ca-certs.pem").getBytes())); X509Certificate[] certifArray = ((X509Certificate[]) request .getAttribute("javax.servlet.request.X509Certificate")); diff --git a/src/main/java/org/onap/clamp/util/PassDecoder.java b/src/main/java/org/onap/clamp/util/PassDecoder.java new file mode 100644 index 00000000..70a47477 --- /dev/null +++ b/src/main/java/org/onap/clamp/util/PassDecoder.java @@ -0,0 +1,74 @@ +/*- + * ============LICENSE_START======================================================= + * ONAP CLAMP + * ================================================================================ + * Copyright (C) 2019 AT&T Intellectual Property. All rights + * reserved. + * ================================================================================ + * 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. + * ============LICENSE_END============================================ + * =================================================================== + */ + +package org.onap.clamp.util; + +import com.att.eelf.configuration.EELFLogger; +import com.att.eelf.configuration.EELFManager; + +import java.io.File; +import java.io.FileInputStream; +import java.io.IOException; +import java.io.InputStream; +import org.onap.aaf.cadi.Symm; +import org.onap.clamp.clds.util.ResourceFileUtil; + +/** + * PassDecoder for decrypting the truststore and keystore password. + */ +public class PassDecoder { + /** + * Used to log PassDecoder class. + */ + private static final EELFLogger logger = EELFManager.getInstance().getLogger(PassDecoder.class); + + /** + * Decode the password. + * @param encryptedPass The encrypted password + * @param keyFileIs The key file in InputStream format + */ + public static String decode(String encryptedPass, String keyFile) { + if (null == keyFile) { + logger.debug("Key file is not defined, thus password will not be decrypted"); + return encryptedPass; + } + if (null == encryptedPass) { + logger.error("Encrypted password is not defined"); + return null; + } + try { + InputStream is; + if (keyFile.contains("classpath:")) { + is = ResourceFileUtil.getResourceAsStream(keyFile.replaceAll("classpath:", "")); + } else { + File key = new File(keyFile); + is = new FileInputStream(key); + } + Symm symm = Symm.obtain(is); + + return symm.depass(encryptedPass); + } catch (IOException e) { + logger.error("Exception occurred during the key decryption", e); + return null; + } + } +} diff --git a/src/main/resources/application-noaaf.properties b/src/main/resources/application-noaaf.properties index 79466c89..d389b211 100644 --- a/src/main/resources/application-noaaf.properties +++ b/src/main/resources/application-noaaf.properties @@ -55,21 +55,25 @@ server.port=8443 ## Config part for Server certificates # Can be a classpath parameter instead of file:/ server.ssl.key-store=classpath:/clds/aaf/org.onap.clamp.p12 -server.ssl.key-store-password=China in the Spring -server.ssl.key-password=China in the Spring +server.ssl.key-store-password=enc:WWCxchk4WGBNSvuzLq3MLjMs5ObRybJtts5AI0XD1Vc +server.ssl.key-password=enc:WWCxchk4WGBNSvuzLq3MLjMs5ObRybJtts5AI0XD1Vc server.ssl.key-store-type=PKCS12 server.ssl.key-alias=clamp@clamp.onap.org ## Config part for Client certificates server.ssl.client-auth=want server.ssl.trust-store=classpath:/clds/aaf/truststoreONAPall.jks -server.ssl.trust-store-password=changeit +server.ssl.trust-store-password=enc:iDnPBBLq_EMidXlMa1FEuBR8TZzYxrCg66vq_XfLHdJ + +# The key file used to decode the key store and trust store password +# If not defined, the key store and trust store password will not be decrypted +clamp.config.keyFile=classpath:/clds/aaf/org.onap.clamp.keyfile #server.http-to-https-redirection.port=8080 server.servlet.context-path=/ #Modified engine-rest applicationpath -spring.profiles.active=clamp-default,clamp-default-user,clamp-sdc-controller-new +spring.profiles.active=clamp-default,clamp-default-user,clamp-sdc-controller-new,clamp-ssl-config spring.http.converters.preferred-json-mapper=gson #The max number of active threads in this pool diff --git a/src/main/resources/application.properties b/src/main/resources/application.properties index 3ac6fa25..b97d6436 100644 --- a/src/main/resources/application.properties +++ b/src/main/resources/application.properties @@ -60,21 +60,25 @@ server.port=8443 ## Config part for Server certificates # Can be a classpath parameter instead of file:/ server.ssl.key-store=classpath:/clds/aaf/org.onap.clamp.p12 -server.ssl.key-store-password=China in the Spring -server.ssl.key-password=China in the Spring +server.ssl.key-store-password=enc:WWCxchk4WGBNSvuzLq3MLjMs5ObRybJtts5AI0XD1Vc +server.ssl.key-password=enc:WWCxchk4WGBNSvuzLq3MLjMs5ObRybJtts5AI0XD1Vc server.ssl.key-store-type=PKCS12 server.ssl.key-alias=clamp@clamp.onap.org +# The key file used to decode the key store and trust store password +# If not defined, the key store and trust store password will not be decrypted +clamp.config.keyFile=classpath:/clds/aaf/org.onap.clamp.keyfile + ## Config part for Client certificates server.ssl.client-auth=want server.ssl.trust-store=classpath:/clds/aaf/truststoreONAPall.jks -server.ssl.trust-store-password=changeit +server.ssl.trust-store-password=enc:iDnPBBLq_EMidXlMa1FEuBR8TZzYxrCg66vq_XfLHdJ #server.http-to-https-redirection.port=8080 server.servlet.context-path=/ #Modified engine-rest applicationpath -spring.profiles.active=clamp-default,clamp-aaf-authentication,clamp-sdc-controller-new +spring.profiles.active=clamp-default,clamp-aaf-authentication,clamp-sdc-controller-new,clamp-ssl-config spring.http.converters.preferred-json-mapper=gson #The max number of active threads in this pool @@ -240,13 +244,10 @@ clamp.config.security.permission.instance=dev clamp.config.security.authentication.class=org.onap.aaf.cadi.principal.X509Principal #AAF related parameters -clamp.config.cadi.keyFile=classpath:/clds/aaf/org.onap.clamp.keyfile clamp.config.cadi.cadiLoglevel=DEBUG clamp.config.cadi.cadiLatitude=10 clamp.config.cadi.cadiLongitude=10 clamp.config.cadi.aafLocateUrl=https://aaf-locate:8095 -clamp.config.cadi.cadiKeystorePassword=enc:WWCxchk4WGBNSvuzLq3MLjMs5ObRybJtts5AI0XD1Vc -clamp.config.cadi.cadiTruststorePassword=enc:iDnPBBLq_EMidXlMa1FEuBR8TZzYxrCg66vq_XfLHdJ clamp.config.cadi.oauthTokenUrl= https://AAF_LOCATE_URL/locate/onap.org.osaaf.aaf.token:2.1/token clamp.config.cadi.oauthIntrospectUrll=https://AAF_LOCATE_URL/locate/onap.org.osaaf.aaf.introspect:2.1/introspect clamp.config.cadi.aafEnv=DEV diff --git a/src/test/java/org/onap/clamp/util/PassDecoderTest.java b/src/test/java/org/onap/clamp/util/PassDecoderTest.java new file mode 100644 index 00000000..56443e31 --- /dev/null +++ b/src/test/java/org/onap/clamp/util/PassDecoderTest.java @@ -0,0 +1,52 @@ +/*- + * ============LICENSE_START======================================================= + * ONAP CLAMP + * ================================================================================ + * Copyright (C) 2019 AT&T Intellectual Property. All rights + * reserved. + * ================================================================================ + * 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. + * ============LICENSE_END============================================ + * =================================================================== + * + */ + +package org.onap.clamp.util; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNull; + +import org.junit.Test; + +public class PassDecoderTest { + + private final String encrypted = "enc:WWCxchk4WGBNSvuzLq3MLjMs5ObRybJtts5AI0XD1Vc"; + + @Test + public final void testDecryptionNoKeyfile() throws Exception { + String decodedPass = PassDecoder.decode(encrypted, null); + assertEquals(decodedPass, encrypted); + } + + @Test + public final void testDecryptionNoPassword() throws Exception { + String decodedPass = PassDecoder.decode(null, "src/test/resources/clds/aaf/org.onap.clamp.keyfile"); + assertNull(decodedPass); + } + + @Test + public final void testDecryption() throws Exception { + String decodedPass = PassDecoder.decode(encrypted, "src/test/resources/clds/aaf/org.onap.clamp.keyfile"); + assertEquals(decodedPass, "China in the Spring"); + } +} diff --git a/src/test/resources/clds/aaf/org.onap.clamp.keyfile b/src/test/resources/clds/aaf/org.onap.clamp.keyfile new file mode 100644 index 00000000..c2521fc8 --- /dev/null +++ b/src/test/resources/clds/aaf/org.onap.clamp.keyfile @@ -0,0 +1,27 @@ +kzJMxgphAoBxJz1_vYjxx-V87fahDQdYUqBIyWhZp8ojXdNpmB-96T9CvgJScJynbLcqw2Cj2CYx +wd97vFOYhlyz5zK3tSyIuydOkVGJsJ1S4PviTtjhiJvNourJNDHgtas1Y1y2fQ5_8aVxj-s4W72N +MNYhkeTinaQx_d_5hkBPABJlgCxKLnmxHo2jAJktnZYa5t5h48m7KiUx_RVEkQVtEvux-7vgXaC4 +ymTXj6zI9XoMTVxM0OAl4y7kBiUoOUaxS4tVKV34RJYNNqBjiUTQa_ag-KeUacRABk1ozfwzpvE5 +Sjz8WCy0L-LtCQnapkhKLt04ndCZtw8LDJ-Zz0ZgR2PVIPpTgs9VnVuOi5jf4LzTrtUatvOWkKB9 +drXKzp6cNXnZ0jkD3vV1BzqzhynKnZR2o_ilZv5CTTdpGUt906N_DwZuX6LfcV_7yvjX42bTfeIR +ycPtodFPXlqqn9VUyh5nOauJlnOHAQmSDzjMEgjy17nQX3Ad7s4BfvujzUl-d0MqB_HCKbaW32UT +xcY-0JfI1Y-2IdYfIkUdhVmxop6sSg0jAobWzgCRoRQkP3a2iIlKdfMyskshoWKIDVtlr-3fkDEb +x_b_o1rRoUfzUzxEdphaUAq80Sc0i77ZLT3KF9vJOhyU_pBnApYFxVk7Hkk3VRxJKS7jyL4H7k1x +2m5-2G8fB9XbYZT82xmAquNx4oBdpwj3_ncGF9YRF94K6NZgqemT5iWhpXMoelSU1blASgT3qlTm +B6YgbD5owExNHwRVd8KeRsYrOnBWUiktsIhXFhNZmDUNWMFGQ2KxEcOt1tJwsQDehJFgY_l1JQ0d +643wJ7rTJkGkYX309cydRQUX4Z0ckSQS9LhMd9stxF5XOHlvHdbW0pXNS7SaLbzKCVldUgncvI6z +KWkwrWbftrZK2RT1UZKNngQDMGOk9OhbHAs7YzhFNFARZoRNobIv5tZVDomy-YgJb9-mD1UTkRBL +WXOyoryDlgKrgFsgHclGDI1UFO5N-JfebPKxbP505f4924hxF2r8bspvVW8ZtHQo_SJmhauOX8n_ +eN_LK43LB9k53WAHZ_utvs0s6wGf7I73oj_N7DIFaHTDSm_MhDsFDLVG_wUzCpZ5FP2uL3nnqMkF +Ob-l1fywfmfOmrz1BY6g4sRPPeWXuclYTnRnDRu5VQyc7_aBEVkyt3zw0JEex0vJNFUJl3pYjS55 +GplAB6p7VbS9ceZEtc5Z3qFIVHEzKWZxT190E23t_LlMuEoQ1zaqdHynNaMs61-q_A2aHRiTqlRm +7FahVB3RX4AVLl23mu4u3A9ZDXc40nzjs9mwOVsuKlPvQ2rteDUG1njr2R1_V_MyQuoJjdfbIkPG +4eF0QzlSMdbkeprdQxSfV5YT-yPpkBxSsCMMM43sKm4Hy7_CUdvp4Iayrp3vtK3oYMuCGi6qTadz +KzxfTf8meKan3eMZW4RLByyniH5nQnX_KGfBly05AmFyVH_j0fyOg-48kDhtEKeqmDnP4C01jOID +Ip_AKaB6e0GwsHzVTLZOklHwu_qzsaTzchBOG_dJJju7bxY7qv78Pa92wZIP311gSCVbc-gxxbsR +qI555twmYEoasFm4xz10OYDOkvM1E1Rtxu3ymRLZpe6AoyFBVzEW7Dncdw7O98dKcgrp8ZlQ_8Wg +5zZH0Cic7xnIZ0bNZyQXw56CSUiXVWuwVY3e0djXP3F-FO5gP8VTxbpW4C0t6McXAOlvSEfFKxN7 +u6OBeOKwjrtHaJk2ghF8MUcpDXanhbAgHez9larGlscCkgvoRLNaRH9GIdSVgY3HtNhJRaJIq01S +OGeBjC5J4o-nTrqRFkwyDAYcPL373eYX1dBFFVHR-4q50H9m_zMxZHXETafxzV4DT3Qi8Sxh3uaS +ZX7mRaNaOE0uC1n87_IZ9WhrwIQaZng2lnd9yZ-4rx8fB8WA8KQzifzvHAcMb_HV10JWGaz5A2Rm +EXDsfexQC6CqYg5rdzzlNWDPNlHy5ubyz7fRXZ99uIwBY9aJcvCXCiEXJkC6utj3NcXQrJmk \ No newline at end of file