Function signatures (ANSI cdecl, x64 Windows) documented in MSAL.NET's + * {@code KeyGuardMaa/AttestationInterop.cs} and also used by msal-go's + * {@code cng_windows.go}:
+ *+ * int InitAttestationLib(AttestationLogInfo*) + * int AttestKeyGuardImportKey(char* endpoint, char* authToken, char* clientPayload, + * NCRYPT_KEY_HANDLE keyHandle, char** token, char* clientId) + * void FreeAttestationToken(char* token) + * void UninitAttestationLib() + *+ * + *
This interface is loaded lazily via {@link AttestationLibraryLoader} from the + * Microsoft-signed DLL bundled in the optional extension. It is only extracted and loaded + * when MAA attestation is requested.
+ */ +interface AttestationLibrary extends Library { + + /** + * No-op log callback that satisfies the DLL's requirement for a non-null LogFunc. + * + *The DLL requires a non-null log function pointer in {@link AttestationLogInfo}. + * Passing {@code Pointer.NULL} causes {@code InitAttestationLib} to return an error + * (0xFFFFFFF8 = -8). Mirrors msal-go's {@code dummyLogCallback}.
+ * + *Signature (cdecl, x64 Windows): + * {@code void LogFunc(void* ctx, char* tag, int lvl, char* func, int line, char* msg)}
+ */ + interface LogCallback extends Callback { + void log(Pointer ctx, Pointer tag, int level, Pointer func, int line, Pointer msg); + } + + /** Shared no-op log callback instance — kept alive to prevent GC. */ + LogCallback NOOP_LOG = (ctx, tag, level, func, line, msg) -> {}; + + /** + * Mirrors the {@code AttestationLogInfo} struct: + *struct AttestationLogInfo { LogFunc Log; void* Ctx; }
+ *
+ * The {@code logFunc} field MUST be a non-null function pointer — the DLL validates + * this and returns an error if it is null. Use {@link #NOOP_LOG} for no-op logging.
+ */ + class AttestationLogInfo extends Structure { + /** Function pointer for the log callback. MUST NOT be null. */ + public LogCallback logFunc; + /** Caller context pointer, passed as first arg to logFunc. */ + public Pointer ctx; + + public AttestationLogInfo() { + logFunc = NOOP_LOG; // DLL requires a non-null log function pointer + ctx = Pointer.NULL; + } + + @Override + protected ListMirrors msal-go's {@code cng_windows.go} and MSAL.NET's + * {@code WindowsManagedIdentityKeyProvider}: creates or opens a persisted RSA key in + * the Microsoft Software Key Storage Provider, using the same 3-level priority:
+ *Magic(4) BitLength(4) cbPublicExp(4) cbModulus(4) cbPrime1(4) cbPrime2(4)+ * followed by PublicExponent bytes then Modulus bytes. + */ + static BigInteger[] exportPublicKey(Pointer hKey) throws MtlsMsiException { + byte[] blob = exportPublicKeyBytes(hKey); + if (blob.length < 24) { + throw new MtlsMsiException("RSAPUBLICBLOB too short: " + blob.length); + } + + ByteBuffer bb = ByteBuffer.wrap(blob).order(ByteOrder.LITTLE_ENDIAN); + bb.getInt(); // magic + bb.getInt(); // bitLength + int cbPublicExp = bb.getInt(); + int cbModulus = bb.getInt(); + // skip cbPrime1, cbPrime2 + bb.position(24); + + byte[] expBytes = new byte[cbPublicExp]; + bb.get(expBytes); + byte[] modBytes = new byte[cbModulus]; + bb.get(modBytes); + + return new BigInteger[] { + new BigInteger(1, modBytes), // [0] = modulus + new BigInteger(1, expBytes) // [1] = publicExponent + }; + } + + private static Pointer openProvider() throws MtlsMsiException { + PointerByReference phProvider = new PointerByReference(); + int ret = NCryptLibrary.INSTANCE.NCryptOpenStorageProvider( + phProvider, new WString(MS_SOFTWARE_KSP), 0); + if (ret != NCryptLibrary.ERROR_SUCCESS) { + throw new MtlsMsiException( + String.format("NCryptOpenStorageProvider failed: 0x%x", ret)); + } + return phProvider.getValue(); + } + + private static void setDwordProperty(Pointer hKey, String propName, int value) + throws MtlsMsiException { + byte[] buf = ByteBuffer.allocate(4).order(ByteOrder.LITTLE_ENDIAN).putInt(value).array(); + int ret = NCryptLibrary.INSTANCE.NCryptSetProperty( + hKey, new WString(propName), buf, buf.length, NCryptLibrary.NCRYPT_SILENT_FLAG); + if (ret != NCryptLibrary.ERROR_SUCCESS) { + throw new MtlsMsiException( + String.format("NCryptSetProperty(%s) failed: 0x%x", propName, ret)); + } + } + + private static boolean isWindows() { + String os = System.getProperty("os.name", "").toLowerCase(); + return os.contains("windows"); + } +} diff --git a/msal4j-mtls-extensions/src/main/java/com/microsoft/aad/msal4j/mtls/CngProvider.java b/msal4j-mtls-extensions/src/main/java/com/microsoft/aad/msal4j/mtls/CngProvider.java new file mode 100644 index 000000000..0ba55a190 --- /dev/null +++ b/msal4j-mtls-extensions/src/main/java/com/microsoft/aad/msal4j/mtls/CngProvider.java @@ -0,0 +1,83 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +package com.microsoft.aad.msal4j.mtls; + +import java.security.Provider; +import java.security.Security; +import java.util.Arrays; +import java.util.Collections; + +/** + * A {@link Provider} that routes RSA signature operations for {@link CngRsaPrivateKey} + * keys through Windows CNG ({@code NCryptSignHash}) via JNA. + * + *
Install once per JVM via {@link #installIfAbsent()} before creating an + * {@code SSLContext} that uses a {@link CngRsaPrivateKey}. JSSE will call + * {@code Signature.getInstance("SHA256withRSA")} or + * {@code Signature.getInstance("RSASSA-PSS")}; with this provider at + * high priority, {@link CngSignatureSpi} intercepts the call and signs via + * {@code NCryptSignHash} instead of requiring an exportable private exponent.
+ * + *For non-{@link CngRsaPrivateKey} keys, {@link CngSignatureSpi} automatically + * delegates to the next available provider, so installing this provider does not + * break other RSA signing in the same JVM.
+ */ +public final class CngProvider extends Provider { + + private static final long serialVersionUID = 1L; + private static final String PROVIDER_NAME = "CNG"; + private static final double PROVIDER_VERSION = 1.0; + private static final String PROVIDER_INFO = "Windows CNG JNA provider for JSSE mTLS"; + + public CngProvider() { + super(PROVIDER_NAME, PROVIDER_VERSION, PROVIDER_INFO); + + putSignature("SHA256withRSA", CngSignatureSpi.Sha256WithRsa.class, + "SHA-256withRSA"); + putSignature("SHA384withRSA", CngSignatureSpi.Sha384WithRsa.class, + "SHA-384withRSA"); + putSignature("SHA512withRSA", CngSignatureSpi.Sha512WithRsa.class, + "SHA-512withRSA"); + putSignature("RSASSA-PSS", CngSignatureSpi.RsaSsaPss.class, + "SHA256withRSAandMGF1", "SHA384withRSAandMGF1", "SHA512withRSAandMGF1"); + } + + private void putSignature( + String algorithm, + Class extends CngSignatureSpi> implementation, + String... aliases) { + putService(new CngSignatureService( + this, + algorithm, + implementation.getName(), + Arrays.asList(aliases))); + } + + /** + * Installs this provider at position 1 (highest priority) if it is not already + * registered. Safe to call multiple times. + */ + public static void installIfAbsent() { + if (Security.getProvider(PROVIDER_NAME) == null) { + Security.insertProviderAt(new CngProvider(), 1); + } + } + + private static final class CngSignatureService extends Provider.Service { + + CngSignatureService( + Provider provider, + String algorithm, + String className, + java.util.ListThis key implements {@link RSAPrivateKey} so that JSSE recognizes it as an RSA key + * and selects RSA cipher suites. The private exponent is {@code null} and + * {@link #getEncoded()} returns {@code null} because the key material never leaves the + * CNG key storage provider (KeyGuard VBS isolation).
+ * + *Signing is performed by {@link CngKeyGuard#signPkcs1} / {@link CngKeyGuard#signPss}, + * dispatched from {@link CngSignatureSpi}.
+ * + *Callers must call {@link #close()} when done to free the CNG handle.
+ */ +public final class CngRsaPrivateKey implements RSAPrivateKey, AutoCloseable { + + private static final long serialVersionUID = 1L; + + private final Pointer handle; + private final BigInteger modulus; + private final int publicExponent; + private final AtomicBoolean closed = new AtomicBoolean(); + private final Runnable releaser; + + CngRsaPrivateKey(Pointer handle, BigInteger modulus, int publicExponent) { + this(handle, modulus, publicExponent, + () -> NCryptLibrary.INSTANCE.NCryptFreeObject(handle)); + } + + CngRsaPrivateKey( + Pointer handle, + BigInteger modulus, + int publicExponent, + Runnable releaser) { + this.handle = handle; + this.modulus = modulus; + this.publicExponent = publicExponent; + this.releaser = releaser; + } + + Pointer nativeHandle() { + if (closed.get()) throw new IllegalStateException("CNG key handle has been closed"); + return handle; + } + + // ─── RSAKey ─────────────────────────────────────────────────────────────── + + /** Returns the RSA modulus (from the exported RSAPUBLICBLOB — public information). */ + @Override + public BigInteger getModulus() { + return modulus; + } + + /** + * Always returns {@code null}. The private exponent is non-exportable from the + * KeyGuard-protected CNG key; signing is delegated to {@code NCryptSignHash}. + */ + @Override + public BigInteger getPrivateExponent() { + return null; + } + + // ─── Key ────────────────────────────────────────────────────────────────── + + @Override + public String getAlgorithm() { return "RSA"; } + + /** Returns {@code null} — non-exportable key has no serializable encoding. */ + @Override + public String getFormat() { return null; } + + /** Returns {@code null} — non-exportable key has no serializable encoding. */ + @Override + public byte[] getEncoded() { return null; } + + // ─── AutoCloseable ──────────────────────────────────────────────────────── + + /** + * Frees the underlying CNG key handle via {@code NCryptFreeObject}. + * The key remains persisted in the KSP; only the in-process handle is released. + */ + @Override + public void close() { + if (closed.compareAndSet(false, true)) { + releaser.run(); + } + } + + boolean isClosed() { + return closed.get(); + } + + /** The public exponent (e.g. 65537 = 0x10001). */ + public int getPublicExponent() { + return publicExponent; + } +} diff --git a/msal4j-mtls-extensions/src/main/java/com/microsoft/aad/msal4j/mtls/CngSignatureSpi.java b/msal4j-mtls-extensions/src/main/java/com/microsoft/aad/msal4j/mtls/CngSignatureSpi.java new file mode 100644 index 000000000..556f293fa --- /dev/null +++ b/msal4j-mtls-extensions/src/main/java/com/microsoft/aad/msal4j/mtls/CngSignatureSpi.java @@ -0,0 +1,303 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +package com.microsoft.aad.msal4j.mtls; + +import com.sun.jna.Pointer; + +import java.security.AlgorithmParameters; +import java.security.InvalidAlgorithmParameterException; +import java.security.InvalidKeyException; +import java.security.MessageDigest; +import java.security.NoSuchAlgorithmException; +import java.security.PrivateKey; +import java.security.Provider; +import java.security.Security; +import java.security.SignatureException; +import java.security.SignatureSpi; +import java.security.spec.AlgorithmParameterSpec; +import java.security.spec.MGF1ParameterSpec; +import java.security.spec.PSSParameterSpec; + +/** + * {@link SignatureSpi} implementations that delegate signing to Windows CNG via JNA. + * + *Two families are provided:
+ *For non-{@link CngRsaPrivateKey} keys, each SPI delegates to the next available + * provider so that installing {@link CngProvider} at high priority does not break other + * code in the same JVM that signs with regular (exportable) RSA keys.
+ */ +abstract class CngSignatureSpi extends SignatureSpi { + + // ─── Concrete algorithms ─────────────────────────────────────────────────── + + /** SHA-256 with RSA PKCS#1 v1.5 */ + public static class Sha256WithRsa extends CngSignatureSpi { + public Sha256WithRsa() { super("SHA-256", "SHA256", false, 32); } + } + + /** SHA-384 with RSA PKCS#1 v1.5 */ + public static class Sha384WithRsa extends CngSignatureSpi { + public Sha384WithRsa() { super("SHA-384", "SHA384", false, 48); } + } + + /** SHA-512 with RSA PKCS#1 v1.5 */ + public static class Sha512WithRsa extends CngSignatureSpi { + public Sha512WithRsa() { super("SHA-512", "SHA512", false, 64); } + } + + /** RSASSA-PSS — algorithm parameters set via {@link #engineSetParameter(AlgorithmParameterSpec)} */ + public static class RsaSsaPss extends CngSignatureSpi { + public RsaSsaPss() { super("SHA-256", "SHA256", true, 32); } + } + + // ─── State ──────────────────────────────────────────────────────────────── + + private final boolean pss; + + // CNG mode + private Pointer cngHandle; + private MessageDigest digest; + private String hashJce; // Java algorithm name (e.g. "SHA-256") + private String hashCng; // CNG algorithm name (e.g. "SHA256") + private int saltLen; + + // Delegation mode (non-CNG keys) + private java.security.Signature delegate; + + CngSignatureSpi(String hashJce, String hashCng, boolean pss, int saltLen) { + this.hashJce = hashJce; + this.hashCng = hashCng; + this.pss = pss; + this.saltLen = saltLen; + } + + // ─── SignatureSpi ───────────────────────────────────────────────────────── + + @Override + protected void engineInitVerify(java.security.PublicKey publicKey) + throws InvalidKeyException { + // CNG only handles signing (NCryptSignHash). For verification (server cert + // validation, etc.) we deliberately throw InvalidKeyException so that + // Signature.Delegate.chooseProvider() skips this SPI and falls through to + // SunRsaSign or another standard provider that handles RSA/ECDSA verification. + throw new InvalidKeyException( + "CngSignatureSpi does not support verification; use SunRsaSign"); + } + + @Override + protected void engineInitSign(PrivateKey key) throws InvalidKeyException { + if (key instanceof CngRsaPrivateKey) { + Pointer h; + try { + h = ((CngRsaPrivateKey) key).nativeHandle(); + } catch (IllegalStateException e) { + throw new InvalidKeyException("CNG key is closed: " + e.getMessage(), e); + } + if (h == null) { + throw new InvalidKeyException("CNG key handle is null (key may be closed or invalid)"); + } + cngHandle = h; + delegate = null; + try { + digest = MessageDigest.getInstance(hashJce); + } catch (NoSuchAlgorithmException e) { + throw new InvalidKeyException("MessageDigest " + hashJce + " not available", e); + } + } else { + // Delegate to the next provider that handles this algorithm. + cngHandle = null; + delegate = null; + try { + delegate = getNextProviderSignature(); + delegate.initSign(key); + } catch (NoSuchAlgorithmException e) { + throw new InvalidKeyException("No fallback provider: " + e.getMessage(), e); + } + } + } + + @Override + protected void engineUpdate(byte b) throws SignatureException { + if (cngHandle != null) { + digest.update(b); + } else if (delegate != null) { + delegate.update(b); + } else { + throw new SignatureException( + "CngSignatureSpi.engineUpdate called before engineInitSign — " + + "Signature object was not properly initialized"); + } + } + + @Override + protected void engineUpdate(byte[] b, int off, int len) throws SignatureException { + if (cngHandle != null) { + digest.update(b, off, len); + } else if (delegate != null) { + delegate.update(b, off, len); + } else { + throw new SignatureException( + "CngSignatureSpi.engineUpdate called before engineInitSign — " + + "Signature object was not properly initialized"); + } + } + + @Override + protected byte[] engineSign() throws SignatureException { + if (cngHandle != null) { + byte[] hash = digest.digest(); + try { + if (pss) { + return CngKeyGuard.signPss(cngHandle, hash, hashCng, saltLen); + } else { + return CngKeyGuard.signPkcs1(cngHandle, hash, hashCng); + } + } catch (MtlsMsiException e) { + throw new SignatureException("CNG signing failed: " + e.getMessage(), e); + } + } else { + return delegate.sign(); + } + } + + @Override + protected boolean engineVerify(byte[] sigBytes) throws SignatureException { + if (delegate != null) { + return delegate.verify(sigBytes); + } + // Verification is not needed for client-auth TLS or CSR generation. + throw new SignatureException("CngSignatureSpi does not support verify (CNG-backed keys)"); + } + + @Override + protected void engineSetParameter(AlgorithmParameterSpec params) + throws InvalidAlgorithmParameterException { + if (!pss) { + if (params != null) { + throw new InvalidAlgorithmParameterException( + "Parameters are not supported for PKCS#1 RSA signatures."); + } + return; + } + if (params instanceof PSSParameterSpec) { + PSSParameterSpec pssSpec = (PSSParameterSpec) params; + validatePssParameters(pssSpec); + hashJce = pssSpec.getDigestAlgorithm(); + hashCng = toCngHashName(pssSpec.getDigestAlgorithm()); + saltLen = pssSpec.getSaltLength(); + if (cngHandle != null) { + // Re-initialize the digest with the new hash algorithm. + try { + digest = MessageDigest.getInstance(hashJce); + } catch (NoSuchAlgorithmException e) { + throw new InvalidAlgorithmParameterException( + "MessageDigest " + hashJce + " not available", e); + } + } else if (delegate != null) { + // Forward PSS params to the delegating provider's Signature instance. + try { + delegate.setParameter(params); + } catch (Exception e) { + throw new InvalidAlgorithmParameterException(e.getMessage(), e); + } + } + } else { + throw new InvalidAlgorithmParameterException( + "RSASSA-PSS requires PSSParameterSpec."); + } + } + + @Override + @SuppressWarnings("deprecation") + protected void engineSetParameter(String param, Object value) { + // Legacy method — no-op, required by abstract superclass. + } + + @Override + @SuppressWarnings("deprecation") + protected Object engineGetParameter(String param) { + return null; + } + + @Override + protected AlgorithmParameters engineGetParameters() { + if (pss && delegate == null) { + try { + AlgorithmParameters ap = AlgorithmParameters.getInstance("RSASSA-PSS"); + ap.init(new PSSParameterSpec(hashJce, "MGF1", + new MGF1ParameterSpec(hashJce), saltLen, 1)); + return ap; + } catch (Exception e) { + return null; + } + } + if (delegate != null) { + return delegate.getParameters(); + } + return null; + } + + // ─── Helpers ────────────────────────────────────────────────────────────── + + private java.security.Signature getNextProviderSignature() throws NoSuchAlgorithmException { + String algName = pss ? "RSASSA-PSS" : hashCng + "withRSA"; + for (Provider p : Security.getProviders()) { + if (p instanceof CngProvider) continue; + if (p.getService("Signature", algName) != null) { + return java.security.Signature.getInstance(algName, p); + } + } + throw new NoSuchAlgorithmException( + "No provider for " + algName + " besides CngProvider"); + } + + private static String toCngHashName(String jceHashName) + throws InvalidAlgorithmParameterException { + if (jceHashName == null) { + throw new InvalidAlgorithmParameterException("PSS digest must be specified."); + } + switch (jceHashName.toUpperCase().replace("-", "")) { + case "SHA256": return "SHA256"; + case "SHA384": return "SHA384"; + case "SHA512": return "SHA512"; + default: + throw new InvalidAlgorithmParameterException( + "Unsupported RSA digest: " + jceHashName); + } + } + + private static void validatePssParameters(PSSParameterSpec spec) + throws InvalidAlgorithmParameterException { + String hash = toCngHashName(spec.getDigestAlgorithm()); + if (!"MGF1".equalsIgnoreCase(spec.getMGFAlgorithm())) { + throw new InvalidAlgorithmParameterException( + "Only MGF1 is supported for RSASSA-PSS."); + } + if (!(spec.getMGFParameters() instanceof MGF1ParameterSpec)) { + throw new InvalidAlgorithmParameterException( + "MGF1 parameters must be MGF1ParameterSpec."); + } + String mgfHash = toCngHashName( + ((MGF1ParameterSpec) spec.getMGFParameters()).getDigestAlgorithm()); + if (!hash.equals(mgfHash)) { + throw new InvalidAlgorithmParameterException( + "PSS digest and MGF1 digest must match."); + } + int expectedSaltLength = "SHA256".equals(hash) ? 32 + : "SHA384".equals(hash) ? 48 : 64; + if (spec.getSaltLength() != expectedSaltLength) { + throw new InvalidAlgorithmParameterException( + "PSS salt length must equal the digest length."); + } + if (spec.getTrailerField() != 1) { + throw new InvalidAlgorithmParameterException( + "Only trailer field 1 is supported for RSASSA-PSS."); + } + } +} diff --git a/msal4j-mtls-extensions/src/main/java/com/microsoft/aad/msal4j/mtls/CngX509ExtendedKeyManager.java b/msal4j-mtls-extensions/src/main/java/com/microsoft/aad/msal4j/mtls/CngX509ExtendedKeyManager.java new file mode 100644 index 000000000..77a993d3c --- /dev/null +++ b/msal4j-mtls-extensions/src/main/java/com/microsoft/aad/msal4j/mtls/CngX509ExtendedKeyManager.java @@ -0,0 +1,95 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +package com.microsoft.aad.msal4j.mtls; + +import javax.net.ssl.SSLEngine; +import javax.net.ssl.X509ExtendedKeyManager; +import java.net.Socket; +import java.security.Principal; +import java.security.PrivateKey; +import java.security.cert.X509Certificate; + +final class CngX509ExtendedKeyManager extends X509ExtendedKeyManager { + + private static final String ALIAS = "msal-keyguard-mtls"; + private final CngRsaPrivateKey privateKey; + private final X509Certificate[] certificateChain; + + CngX509ExtendedKeyManager( + CngRsaPrivateKey privateKey, + X509Certificate certificate) { + this.privateKey = privateKey; + this.certificateChain = new X509Certificate[]{certificate}; + } + + @Override + public String[] getClientAliases(String keyType, Principal[] issuers) { + return supportsKeyType(keyType) ? new String[]{ALIAS} : null; + } + + @Override + public String chooseClientAlias( + String[] keyTypes, + Principal[] issuers, + Socket socket) { + return supportsAnyKeyType(keyTypes) ? ALIAS : null; + } + + @Override + public String chooseEngineClientAlias( + String[] keyTypes, + Principal[] issuers, + SSLEngine engine) { + return supportsAnyKeyType(keyTypes) ? ALIAS : null; + } + + @Override + public X509Certificate[] getCertificateChain(String alias) { + return ALIAS.equals(alias) ? certificateChain.clone() : null; + } + + @Override + public PrivateKey getPrivateKey(String alias) { + return ALIAS.equals(alias) ? privateKey : null; + } + + @Override + public String[] getServerAliases(String keyType, Principal[] issuers) { + return null; + } + + @Override + public String chooseServerAlias( + String keyType, + Principal[] issuers, + Socket socket) { + return null; + } + + @Override + public String chooseEngineServerAlias( + String keyType, + Principal[] issuers, + SSLEngine engine) { + return null; + } + + private static boolean supportsAnyKeyType(String[] keyTypes) { + if (keyTypes == null) { + return false; + } + for (String keyType : keyTypes) { + if (supportsKeyType(keyType)) { + return true; + } + } + return false; + } + + private static boolean supportsKeyType(String keyType) { + return keyType != null + && ("RSA".equalsIgnoreCase(keyType) + || "RSASSA-PSS".equalsIgnoreCase(keyType)); + } +} diff --git a/msal4j-mtls-extensions/src/main/java/com/microsoft/aad/msal4j/mtls/ImdsV2Client.java b/msal4j-mtls-extensions/src/main/java/com/microsoft/aad/msal4j/mtls/ImdsV2Client.java new file mode 100644 index 000000000..60418efe1 --- /dev/null +++ b/msal4j-mtls-extensions/src/main/java/com/microsoft/aad/msal4j/mtls/ImdsV2Client.java @@ -0,0 +1,264 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +package com.microsoft.aad.msal4j.mtls; + +import com.microsoft.aad.msal4j.ManagedIdentityMtlsHttpRequest; +import com.microsoft.aad.msal4j.ManagedIdentityMtlsHttpResponse; +import com.microsoft.aad.msal4j.ManagedIdentityMtlsRequest; + +import java.net.URLEncoder; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; + +final class ImdsV2Client { + + private static final String IMDS_BASE = "http://169.254.169.254"; + private static final String PLATFORM_METADATA_PATH = + "/metadata/identity/getplatformmetadata"; + private static final String ISSUE_CREDENTIAL_PATH = + "/metadata/identity/issuecredential"; + private static final String API_VERSION_QUERY = "cred-api-version=2.0"; + + private ImdsV2Client() { + } + + static PlatformMetadata getPlatformMetadata(ManagedIdentityMtlsRequest request) { + ManagedIdentityMtlsHttpResponse httpResponse = execute( + request, + "GET", + buildUrl(PLATFORM_METADATA_PATH, request), + null); + validateImdsOrigin(httpResponse); + String response = httpResponse.body(); + PlatformMetadata metadata = new PlatformMetadata( + extractString(response, "clientId"), + extractString(response, "tenantId"), + extractNestedString(response, "cuId", "vmId"), + extractNestedString(response, "cuId", "vmssId"), + extractString(response, "attestationEndpoint")); + if (isBlank(metadata.clientId) + || isBlank(metadata.tenantId) + || isBlank(metadata.cuId()) + || (request.attestationEnabled() + && isBlank(metadata.attestationEndpoint))) { + throw new MtlsMsiException( + "IMDS getplatformmetadata returned an incomplete attested KeyGuard contract."); + } + return metadata; + } + + static CredentialResponse issueCredential( + ManagedIdentityMtlsRequest request, + String csr, + String attestationToken) { + if (request.attestationEnabled() && isBlank(attestationToken)) { + throw new MtlsMsiException( + "KeyGuard attestation failed; no attestation token was produced."); + } + String body = "{\"csr\":\"" + escapeJson(csr) + "\"" + + (isBlank(attestationToken) + ? "" + : ",\"attestation_token\":\"" + + escapeJson(attestationToken) + "\"") + + "}"; + String response = execute( + request, + "POST", + buildUrl(ISSUE_CREDENTIAL_PATH, request), + body).body(); + CredentialResponse credential = new CredentialResponse( + extractString(response, "certificate"), + extractString(response, "mtls_authentication_endpoint"), + extractString(response, "client_id"), + extractString(response, "tenant_id"), + extractString(response, "identity_type")); + if (isBlank(credential.certificate) + || isBlank(credential.mtlsAuthenticationEndpoint) + || isBlank(credential.clientId) + || isBlank(credential.tenantId) + || isBlank(credential.identityType)) { + throw new MtlsMsiException( + "IMDS issuecredential returned an incomplete binding credential."); + } + return credential; + } + + private static ManagedIdentityMtlsHttpResponse execute( + ManagedIdentityMtlsRequest request, + String method, + String url, + String body) { + MapFunction signatures mirror MSAL.NET's {@code WindowsCngKeyOperations} and msal-go's + * {@code cng_windows.go}. All NCrypt functions follow the Windows x64 calling convention + * (which equals cdecl on x64).
+ */ +interface NCryptLibrary extends Library { + + NCryptLibrary INSTANCE = Native.load("ncrypt", NCryptLibrary.class); + + // ─── NCrypt constants ────────────────────────────────────────────────────── + + int ERROR_SUCCESS = 0; + + int NCRYPT_SILENT_FLAG = 0x00000040; + int NCRYPT_OVERWRITE_KEY_FLAG = 0x00000080; + int NCRYPT_MACHINE_KEY_FLAG = 0x00000020; // not used (USER scope only) + int NCRYPT_USE_VIRTUAL_ISOLATION_FLAG = 0x00020000; // VBS KeyGuard + int NCRYPT_USE_PER_BOOT_KEY_FLAG = 0x00040000; // ephemeral per boot + int NCRYPT_ALLOW_EXPORT_NONE = 0; // non-exportable + + int NCRYPT_PAD_PKCS1_FLAG = 0x00000002; + int NCRYPT_PAD_PSS_FLAG = 0x00000008; + + // ─── Padding info structures ─────────────────────────────────────────────── + + /** Maps to {@code BCRYPT_PKCS1_PADDING_INFO} — used with NCRYPT_PAD_PKCS1_FLAG. */ + class BcryptPkcs1PaddingInfo extends Structure { + /** Algorithm name for the hash (e.g. L"SHA256"). LPCWSTR in C. */ + public WString pszAlgId; + + public BcryptPkcs1PaddingInfo(String algName) { + pszAlgId = new WString(algName); + write(); + } + + @Override + protected List
+ * CertificationRequest ::= SEQUENCE {
+ * certificationRequestInfo CertificationRequestInfo,
+ * signatureAlgorithm AlgorithmIdentifier, -- RSASSA-PSS with SHA-256 params
+ * signature BIT STRING
+ * }
+ *
+ * CertificationRequestInfo ::= SEQUENCE {
+ * version INTEGER { v1(0) }
+ * subject Name -- CN={clientId}, DC={tenantId}
+ * subjectPKInfo SubjectPublicKeyInfo
+ * attributes [0] IMPLICIT SET OF -- OID 1.3.6.1.4.1.311.90.2.10 = cuId JSON
+ * }
+ *
+ *
+ * This is a pure-Java port of msal-go's {@code generateCSR()} in {@code imdsv2.go}, + * using manual DER encoding to avoid adding external ASN.1 library dependencies.
+ */ +final class Pkcs10Builder { + + private Pkcs10Builder() {} + + // ─── OIDs (pre-encoded DER) ──────────────────────────────────────────────── + + // rsaEncryption: 1.2.840.113549.1.1.1 + private static final byte[] OID_RSA_ENCRYPTION = hexToBytes("2a864886f70d010101"); + // sha256: 2.16.840.1.101.3.4.2.1 + private static final byte[] OID_SHA256 = hexToBytes("608648016503040201"); + // mgf1: 1.2.840.113549.1.1.8 + private static final byte[] OID_MGF1 = hexToBytes("2a864886f70d010108"); + // id-RSASSA-PSS: 1.2.840.113549.1.1.10 + private static final byte[] OID_RSASSA_PSS = hexToBytes("2a864886f70d01010a"); + // commonName: 2.5.4.3 + private static final byte[] OID_COMMON_NAME = hexToBytes("5504 03".replace(" ", "")); + // domainComponent: 0.9.2342.19200300.100.1.25 + private static final byte[] OID_DOMAIN_COMPONENT = hexToBytes("0992268993f22c6401 19".replace(" ", "")); + // cuId attribute: 1.3.6.1.4.1.311.90.2.10 + private static final byte[] OID_CU_ID = hexToBytes("2b060104018237 5a02 0a".replace(" ", "")); + + // ─── Public API ─────────────────────────────────────────────────────────── + + /** + * Generates a PKCS#10 CSR and returns it as standard Base64-encoded DER + * (no PEM headers), ready to be placed in the {@code csr} field of the + * IMDS {@code /issuecredential} JSON request. + * + * @param keyHandle CNG key handle (the private key — signs the CSR TBS bytes) + * @param modulus RSA public key modulus (from {@link CngKeyGuard#exportPublicKey}) + * @param publicExp RSA public exponent + * @param clientId managed identity client ID → CN in subject + * @param tenantId tenant GUID → DC in subject + * @param vmId VM ID for the cuId attribute ({@code cuId.vmId}); may be null + * @param vmssId VMSS ID for the cuId attribute; may be null + * @return Base64-encoded DER of the PKCS#10 CSR + */ + static String generate(Pointer keyHandle, BigInteger modulus, int publicExp, + String clientId, String tenantId, String vmId, String vmssId) + throws MtlsMsiException { + + // --- SubjectPublicKeyInfo ------------------------------------------ + byte[] spki = buildSpki(modulus, publicExp); + + // --- Subject: CN={clientId}, DC={tenantId} ------------------------- + byte[] subject = buildSubject(clientId, tenantId); + + // --- cuId attribute ------------------------------------------------ + byte[] cuIdJson = buildCuIdJson(vmId, vmssId); + byte[] attributes = buildCuIdAttribute(cuIdJson); + + // --- CertificationRequestInfo SEQUENCE ----------------------------- + byte[] version = derInteger(new byte[]{0x00}); // INTEGER v1(0) + byte[] certReqInfo = derSequence(concat(version, subject, spki, attributes)); + + // --- Sign with RSASSA-PSS SHA-256 (salt=32) ----------------------- + byte[] tbs; + try { + tbs = MessageDigest.getInstance("SHA-256").digest(certReqInfo); + } catch (NoSuchAlgorithmException e) { + throw new MtlsMsiException("SHA-256 not available: " + e.getMessage(), e); + } + byte[] sig = CngKeyGuard.signPss(keyHandle, tbs, "SHA256", 32); + + // --- AlgorithmIdentifier for RSASSA-PSS ---------------------------- + byte[] sigAlgId = buildPssAlgorithmIdentifier(); + + // --- BIT STRING wrapping the signature ----------------------------- + byte[] sigBitString = derBitString(sig); + + // --- Final CertificationRequest SEQUENCE --------------------------- + byte[] csr = derSequence(concat(certReqInfo, sigAlgId, sigBitString)); + + return Base64.getEncoder().encodeToString(csr); + } + + // ─── DER building blocks ────────────────────────────────────────────────── + + /** DER SEQUENCE */ + static byte[] derSequence(byte[] content) { + return derTagLen(0x30, content); + } + + /** DER SET */ + private static byte[] derSet(byte[] content) { + return derTagLen(0x31, content); + } + + /** DER INTEGER from raw bytes (big-endian, with sign byte if high bit set) */ + private static byte[] derInteger(byte[] value) { + // Add leading 0x00 if high bit is set (unsigned → signed two's complement). + byte[] content = (value[0] & 0x80) != 0 + ? concat(new byte[]{0x00}, value) + : value; + return derTagLen(0x02, content); + } + + /** DER OBJECT IDENTIFIER from pre-encoded OID value bytes */ + private static byte[] derOid(byte[] oidBytes) { + return derTagLen(0x06, oidBytes); + } + + /** DER UTF8String */ + private static byte[] derUtf8String(String s) { + byte[] bytes = s.getBytes(java.nio.charset.StandardCharsets.UTF_8); + return derTagLen(0x0C, bytes); + } + + /** DER BIT STRING — prepend 0x00 (zero unused bits) */ + static byte[] derBitString(byte[] data) { + byte[] content = new byte[data.length + 1]; + content[0] = 0x00; + System.arraycopy(data, 0, content, 1, data.length); + return derTagLen(0x03, content); + } + + /** DER NULL */ + private static final byte[] DER_NULL = {0x05, 0x00}; + + /** Context-specific explicit tag [N] wrapping content */ + private static byte[] contextExplicit(int n, byte[] content) { + return derTagLen(0xA0 | n, content); + } + + /** Context-specific implicit tag [N] wrapping content */ + private static byte[] contextImplicit(int n, byte[] content) { + return derTagLen(0x80 | n, content); + } + + /** Writes tag + DER length + content */ + private static byte[] derTagLen(int tag, byte[] content) { + ByteArrayOutputStream out = new ByteArrayOutputStream(); + out.write(tag); + int len = content.length; + if (len < 0x80) { + out.write(len); + } else if (len < 0x100) { + out.write(0x81); + out.write(len); + } else if (len < 0x10000) { + out.write(0x82); + out.write((len >> 8) & 0xFF); + out.write(len & 0xFF); + } else { + out.write(0x83); + out.write((len >> 16) & 0xFF); + out.write((len >> 8) & 0xFF); + out.write(len & 0xFF); + } + try { out.write(content); } catch (java.io.IOException ignored) {} + return out.toByteArray(); + } + + // ─── Component builders ─────────────────────────────────────────────────── + + /** + * SubjectPublicKeyInfo ::= SEQUENCE { algorithm AlgorithmIdentifier, subjectPublicKey BIT STRING } + * AlgorithmIdentifier for RSA: SEQUENCE { OID rsaEncryption, NULL } + * Public key: BIT STRING containing RSAPublicKey SEQUENCE { modulus INTEGER, publicExp INTEGER } + */ + private static byte[] buildSpki(BigInteger modulus, int publicExp) { + // RSAPublicKey SEQUENCE { modulus INTEGER, publicExp INTEGER } + byte[] modBytes = modulus.toByteArray(); + byte[] expBytes = BigInteger.valueOf(publicExp).toByteArray(); + byte[] rsaPublicKey = derSequence(concat(derInteger(modBytes), derInteger(expBytes))); + + // AlgorithmIdentifier for rsaEncryption + byte[] algId = derSequence(concat(derOid(OID_RSA_ENCRYPTION), DER_NULL)); + + // SubjectPublicKeyInfo + return derSequence(concat(algId, derBitString(rsaPublicKey))); + } + + /** + * Name ::= SEQUENCE { RDN SEQUENCE { AttributeTypeAndValue SEQUENCE { OID, value } } } + * Subject: CN={clientId}, DC={tenantId} + * Matches msal-go: pkix.Name{CommonName: clientId, ExtraNames: []pkix.AttributeTypeAndValue{{Type: dcOID, Value: tenantId}}} + */ + private static byte[] buildSubject(String clientId, String tenantId) { + // AttributeTypeAndValue SEQUENCE { OID commonName, UTF8String clientId } + byte[] cnAttr = derSequence(concat(derOid(OID_COMMON_NAME), derUtf8String(clientId))); + byte[] cnRdn = derSet(cnAttr); + + // AttributeTypeAndValue SEQUENCE { OID domainComponent, UTF8String tenantId } + byte[] dcAttr = derSequence(concat(derOid(OID_DOMAIN_COMPONENT), derUtf8String(tenantId))); + byte[] dcRdn = derSet(dcAttr); + + // Name = SEQUENCE of RDNs + return derSequence(concat(cnRdn, dcRdn)); + } + + /** + * Builds the cuId JSON string. Matches msal-go's json.Marshal(cuID): + * {@code {"vmId":"Per PKCS#10, {@code [0] IMPLICIT Attributes} — IMPLICIT tagging of a constructed type + * keeps the constructed bit, so the tag byte is {@code 0xA0} (context-specific, constructed). + * Mirrors msal-go's {@code buildCuIDAttribute()} which uses + * {@code asn1.RawValue{Class: ClassContextSpecific, Tag: 0, IsCompound: true}}.
+ */ + private static byte[] buildCuIdAttribute(byte[] cuIdJsonBytes) { + byte[] utf8Str = derTagLen(0x0C, cuIdJsonBytes); // UTF8String + byte[] valueSet = derSet(utf8Str); // SET { UTF8String } + byte[] attrSeq = derSequence(concat(derOid(OID_CU_ID), valueSet)); // SEQUENCE { OID, SET } + return contextExplicit(0, attrSeq); // [0] CONSTRUCTED { SEQUENCE } — 0xA0 tag + } + + /** + * AlgorithmIdentifier for RSASSA-PSS with SHA-256: + * SEQUENCE { + * OID id-RSASSA-PSS, + * SEQUENCE { -- RSASSA-PSS-params + * [0] SEQUENCE { OID sha-256, NULL }, -- hashAlgorithm + * [1] SEQUENCE { OID mgf1, SEQUENCE { OID sha-256, NULL } }, -- maskGenAlgorithm + * [2] INTEGER 32 -- saltLength + * } + * } + * Matches msal-go's explicit PSS AlgorithmIdentifier. + */ + private static byte[] buildPssAlgorithmIdentifier() { + // sha256AlgID: SEQUENCE { OID sha256, NULL } + byte[] sha256AlgId = derSequence(concat(derOid(OID_SHA256), DER_NULL)); + + // hashAlgorithm [0]: sha256AlgID + byte[] hashAlgorithm = contextExplicit(0, sha256AlgId); + + // mgf1AlgID: SEQUENCE { OID mgf1, sha256AlgID } + byte[] mgf1AlgId = derSequence(concat(derOid(OID_MGF1), sha256AlgId)); + // maskGenAlgorithm [1]: mgf1AlgID + byte[] maskGenAlgorithm = contextExplicit(1, mgf1AlgId); + + // saltLength [2]: INTEGER 32 + byte[] saltLength = contextExplicit(2, derInteger(new byte[]{32})); + + // RSASSA-PSS-params SEQUENCE + byte[] pssParams = derSequence(concat(hashAlgorithm, maskGenAlgorithm, saltLength)); + + // AlgorithmIdentifier SEQUENCE { OID id-RSASSA-PSS, pssParams } + return derSequence(concat(derOid(OID_RSASSA_PSS), pssParams)); + } + + // ─── Utility ────────────────────────────────────────────────────────────── + + private static byte[] concat(byte[]... arrays) { + int total = 0; + for (byte[] a : arrays) total += a.length; + byte[] result = new byte[total]; + int offset = 0; + for (byte[] a : arrays) { + System.arraycopy(a, 0, result, offset, a.length); + offset += a.length; + } + return result; + } + + private static byte[] hexToBytes(String hex) { + hex = hex.replace(" ", ""); + byte[] result = new byte[hex.length() / 2]; + for (int i = 0; i < result.length; i++) { + result[i] = (byte) Integer.parseInt(hex.substring(i * 2, i * 2 + 2), 16); + } + return result; + } +} diff --git a/msal4j-mtls-extensions/src/main/resources/META-INF/LICENSE-Microsoft.Azure.Security.KeyGuardAttestation.txt b/msal4j-mtls-extensions/src/main/resources/META-INF/LICENSE-Microsoft.Azure.Security.KeyGuardAttestation.txt new file mode 100644 index 000000000..88040d887 --- /dev/null +++ b/msal4j-mtls-extensions/src/main/resources/META-INF/LICENSE-Microsoft.Azure.Security.KeyGuardAttestation.txt @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) Microsoft Corporation. All rights reserved. + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/msal4j-mtls-extensions/src/main/resources/META-INF/NOTICE-Microsoft.Azure.Security.KeyGuardAttestation.txt b/msal4j-mtls-extensions/src/main/resources/META-INF/NOTICE-Microsoft.Azure.Security.KeyGuardAttestation.txt new file mode 100644 index 000000000..dcf47bfdf --- /dev/null +++ b/msal4j-mtls-extensions/src/main/resources/META-INF/NOTICE-Microsoft.Azure.Security.KeyGuardAttestation.txt @@ -0,0 +1,5 @@ +This artifact includes AttestationClientLib.dll from +Microsoft.Azure.Security.KeyGuardAttestation 1.1.5. + +Copyright (c) Microsoft Corporation. All rights reserved. +Licensed under the MIT License. diff --git a/msal4j-mtls-extensions/src/main/resources/META-INF/native/win-x64/AttestationClientLib.dll b/msal4j-mtls-extensions/src/main/resources/META-INF/native/win-x64/AttestationClientLib.dll new file mode 100644 index 0000000000000000000000000000000000000000..bb2695ef880479947c19f5a1ba8212b72bd1bf13 GIT binary patch literal 5569056 zcmeEv34Bw<`gTf7TcB`K0YOC#8ni{k)+)CsVgjvAAc3;)P}xx+wF{u4ZD|o=jMg1@ z+;_#TB5qA-+0n8uUD=ZsNTEQXK>IzurtzOu!U+XgGtlODjX@MnmX+eb=-4uIF`K%XMAL z^x?bp;oJ1)lX~-MW7iyG*CK;?o55TLlPX}aH3%t>;i|sQ5W39}TBh%MPT%!{JLMjI z_;z>74aTmAJSp|zu*r@E=);j6(+#$EXf@c{L#+?D>BBeayH@MFUUXC0s}J8{2+uc! zJB;D^#&B5dFg`aJ!?);cF@J%a6$o9`DgY9UIN-w6;844(M%mrqLrGq|;k4Xxfx|sA{b( z7uT=EXl~0^8ZEC-K}Urn;cBS%eMZYytc$4d)luPl9aCLeyMey?U!t$GZfs6p>ukq- z^*+b#Nq>{tZhk!S8jRA92HJ0iFm;yM>JXh}ygE#mo*UWZ2w*;;GuP U;a`7#xH)e@a*nXo*{5}j=sQEG`+SSF}&qp^&LOFPG^Oj4(v z%f?m8cpGloIaZ;R5yoD$HoFE?9l@k&@z+^KVjUttXC4*E#uvW6yd?%j8q848 {m9OB91aGvqdI%b ztjoW|BlrT3YQZD;fy{cOpYu-|3m(lm7 kvFuU8?c2Dz$OY37(PQL?)t zVmg~+A|qnDm}B~&lq!b-O<-+ISUb6($?Bq-@DXl3pyZl^P&=~?F~!BB;+cUAH(oweon?J4ni z^p*u-I?K#Rqj{}1{kRXt&lG5>06lEA|7!k!Me|ebQ{`jWfOdB)d0D$_%>i07CdFn< z;?1y#A$V!=B4`WrR0G$o3|zM|K$~?oSf;8~m(XiXw;zLfy#ebYAvB!Bgz=8mDJdhh zr9$zhM{iz;Ml|c)pCZ&??YKV}gFRLVjZ)OfC`-rodxj` (tWhrd173sm7xfD8u1`ObZ8V=WTHjW*g6(d;pqG~+^;p?MLVemnUW~m969SE) zg~re$<(-V7+g-u>v|J6EH9|2}F)X=Yz%&H?exVwAAPE964~JsM>QR`%9How2o1tcG z{u#`9uu4QzJ*zkWskJO&zc8AsjiD#&7^o&k2|;?Sx`Ks->zD^d0qa6_tbxu?Hy}-= z?!!!T0B0pXU4>i3F 7=(zBBIoqi1F&E2!mP|F
n0?3}b@x@|QU1 zM;gpG8q71$x9Uw6Gt+;On~kV=DPss4uc&$Sx=P*WnGOwGxplVs6J7eDY=iarS*Qj* z?9-~fXi1#l8bWvImK>#cASkrM28~aT6>quyu@%`${q$Bp46|6DbWPH9)(rJocA3x$ zT{qVZ*Pm@j%GeI^xoY0KIVGi_s+((526PCOD7Ssbu{udI?DCpe@<+eLDG?#?353 zZf1J(YQ1@r!Mp`yi~+AtKA4{QlM+b`p<5`n&!%V6^j#%_%)1TdeU3qpnx}nYnHu)s z8nhL(muZ2XYGk>ZX{%o4y4}-4+~vANGf9X}y4jOMgX(wpv2u))&9;&9`@xs@bJU&& zO)m _cZTZ!6}C)*M`AAa~(qS$2mhIl?NZ#WSb78Xmi z@bU2_{+Kc2Q)1+8w>?1|sj0b!_+i(s$%OIq&kqwv=goVacy7anXyV32i{2v4>(^_D z>L;F9LO83dI}+cH8B L&WS}y~Ywx-haQAI9*lM zf=J)AX)rPKuDiw)Lq7cQ6tU&dp}UB4Cr$(s`yYAaMdI__yAz4Y;o-xG?j1YcPXzk; z1raZ=T|1O`;O3jB5pS(tJ&_3O+&P5!$1AVAOSEp;QX=kn{q-M+b^!sc2=!H0^&+lI zO?`tX*}wljVsm- kH*Y+fO zU3cB>MB&`I-xA-C9ebP@w|x0|;)9_>&k*zOz1K+mxO3-r3r_XjaFH1u(z=)i%# z#Kb%9c!X%5k?|_=(Xe5siJ!J?nMptuh(Fu0V?79sN)<;OxNzY{;?ZxuSx01z8+VM@ z{?J3e5!Q_xA0TG@_S+sJp?B|r#7hGP6cft~hV8_P(WC9e;y?c!K>VDa|1go~bap2O zfA-lS;@J7~R})Y5@1IA!ZZz&9rbk4KAc7Y!eu;QGI{Ht-aqgUw@N3b+pQuSmNh6lW z##Rsu`t*q+y1nzxDq`R#pBy0G8#1JlnDGAlHAJdLlTEaI?zyLl`(JqBOQP#rZ~a2V zM@H(16Q@u65mSEorG%KDm1QHo-n%!M=-0LD6hfs`h7pHqYp*3PR#vtne&4!v4w1Hb zvw`Rw6x4yZrG5LmiPcAs-a-5}Y0^I8?pI%Zk60QNwTbB2sZ(#_#oXLc#E6eSK1p17 z^2tTSWB1(`L#&%R^$TLvgb7E8JFmIsHsWw%;>$#KT- CCy9GAGe0NVEM1yTJoCT<8;G^XkB1TuuUxr}2 ()0AFX;6JM3BFKYeJ`1&n8xmAAgW| zCnskD@$0s2bBX?6eBmT=Cr|!}=>7cjpAg|Mzx)BQ?cl+G5GNmf)I#hya^z0JvTj{} zV(vfwF^1SXXU>m=kJb7N@y(7MQ;9bR4cbcRKmD|n*gbpp_rw7ph^ee}@@ zBJun0*AlO;Su=##Xt&=?yjxVXkgz8uy-D=F@y4#iD+31>5fA?QYa!9rVtI;?o_=~U zQ5qkgNi6K!w;%Dy4?pA*o2F0yjL6jKb`$=-zAcHWGiQ8=F74V~MdUpA;QPcwci%mU z_-fRsbHuEle#$30J@wS{#A`O&XyTshufLVJ_3gKRBKGXt7fF<#J=>nRZP~K#h?|0g zBZ#fDX8lZjx@S*35%BD@W+LLHmp&$Lf8&iTqR$OC+(dL-vg8F~y~Cj*mi+ODp7?CU zh*}~xBxC{cVM)njgzq!YJV%Uq_uaEZE3-M3xVCNEPQ n#kDq+l zlo#eR N{X#Eq_V`3`4-2UFzZ+?)F{o} VQU>cThU9@_X@?*WF Am9>>yHwU#pI_cG@PPrdHdEe9t*CfWJ z4KBQ?d*;#yj<4)cJh4qd>7Dnq*88i+=WP38^7AhreDuh=f6TG&81(7vk*CV0J@!@L zss-J8cKPW0HTI&U8wdVsc{;xD57TwNXWBh@_o$zqvR(i7zO&1MXYG0Rr8jO^;`n1k zNXawrn%n+ h`v3pA{d3s^Ge`w@ePMZ8?6p+kssU1g>wsSL zyZ-oAn}2+x&A%b1K8_`x)J_{lT-)h`)x )VTkeiZC4QLv{&Pg}H)Higw^i58 zAfn6KEh8q>WbY$Z-;vycNHbJiMOYtr^?l;uZRgeyzRyiMLUerL&6kPa=R8(UygYQw zUSj)i>9-T3le%sr^rf>xiD%cZiz1S9%nuQ#o_%xy@!IIV4->ck^hgo$+rGhBM8(oC zI}z(uUrZ;S_~V}Mi76!mIunPk4PQjOIwT^Fm^>^cn)qhw{8HlQhrh}oYSNZ>A m_zF8bm%#7o7qmk>iAKl>x`dE&O4iG+bI_7ewgd~OsmeZ=S8iEqn80*E_) z$nhnf%6t5EV%t9gKP19JmYgJR{{6#mi59*Ots;(Hy=Xk~^q*aBBR+og`xl7HfRh!( zpjrFhC8q5?wVCj5`P&X+%UzRiByvBhd78MZ=dibk_bNv$Ct5ze?@r=_Gv&!doAjf- z2~C{yGh*T+@AwnH&Q%T}`gVQ#9Pvt#|9wPJ- 2lpKs-O(`UCMu?u3?vpZ|6f z5f|IBmbhlwc{Q=~SI4V_S<` 2 z`&Od0v}Y A%%B;xzyuWTi*d*k`HiPY@8!Nh}w zs`rSo^9Rl%x?FYb)x`c6SG6Xxmv=uz^tq|DKXKJ{ovtU^wQAdjIB8k?C2@P!CqEG# zZuoQ^G3vxMi;1t30}RCR*NT27-cO0UNL2aU@(wXR;q)&=)bxAvh;iqiyo<2(51mJB zym?I_k#+3COk(B16H|y?;e{KBep41JiT1DFq#-ia1gnU}1Jq9tTYtXqEb-O3JA;X} zq3_Niu7CTEfkffAVU ?UrC$aE4*^n=zCdm^{kh-WsuS3@k@-liAv(do25i4Pwu4k7mb`0Opj z{a^n60ny{0w+<4q3tnhTJTz(38^rJ@ADB#>v9`}4GIy`E6FqxZO(Yhr{`xfW&V >&o2di_C^z0+zt5xQdGP9pq+ zZ@(tG-+yyBvF5{2rakfct_PnX{_$?mW5k%V z9iAo1+xIaOe|&oDapK1Z*WXR->vHHA@mS0^eTX-=mW&}roJ_o*=-lCtM~TF>|M;C4 zR<*f|m~`>8aYV^|rVhk{sDh`64PRHL5kJk$?Mp *J8#^3`Z<;@-4f28nflbsWmj+iVEm>D)3RQe9j}YMc SOC$ z8JW93S3L8}w$@b#N4H%0U9^9Gj~kxbaKF^T_Vus6KJA}=_@3v3wS#9Amc4rN&p#@@ zKb-i=7fClA`SR|?`_KKu-X*zYZp?{mAGtm>&usc}a=Xtu|90Jlk$nfQ+4`vJ^@8l0 z1)Im7y0Pe!h@}B*!@s#oIp)^Xqm$x>q }cUa_wH z=RWDfUVE_9ZGKO#n!odweX}}D8PPT9iRSDgxBmaHd}Im9R-?ym{I7ie+kXCz{-uo` zrkZX_P%Vh;HL2HKJyiXNj)@&UI7Vw6rkebys?US*_!>`Rik~(&F>!WJQ^JhCUhg{j zZe-*r&-?2)vD;Lmw`dmVEz9^FmEQ75pwTiWGTPF9khR0rhThu}#_BDp{8B60GR)5v zmiU0qqQN_v(Py#mp~7H^vl}ckOAVHI>>^!|uQxBtBq{SJ%W*N9m+uyrPfEpQg 2Gf5fUu+UsUxN3H zy|+n=o=1G#k0}-R3H{->;Ugo;JP7_Gm;W}G{1N1fkc# f;ZUZ#0VJ4riHJ3%{s!njEj zD_39U_Zxci7VXb`TWVRvMl@Y3negq7spCudmT|gNvaS*Vq0^;=Eq-`ZV4E&QD)RBX zWrlbjsX&Nxhd$k=#5D&}?1C*+<&SyZ8j|24I=M``pU!GT6+upTa;Nu~OosOOW++lo zM!&i}WAEhsHe|G$^m$`*XTqC8efnzVh^OMo0c^0d`ygO76R)RJPVVb7ZfE#j#1h5W z!#gnKEFzIo-tmj2L%iGOj)}4&H YA@YVd*@8VmTyHzjK*0SGO`l#F2AR$jDO?2wZOFrP`}^2Q2Y(L5V)WVO zconq=u?yZrVFpyVc#BkQ6bymio8YlA$^b~uRULWs)y0^ss*4iVTojg1rV`sk6uF-6 zk&Xp1ny43Kh(?9#fCrUYzcC@Kah)K9bs{ |)C8XoA zZt?nL|4J#PMR|MHbdV6@9t2#gPp=3|tI0+k%%78V-EZ9GXzvP@tE>G(%Amrx`+`(? zIllKZ#RS^d;1TFnfTe@dRi}RA>Y4onoaxd%(wac~PCU2U7|3sx?YrOurKF+uUwKQt zUY;-aG161o=OOqveivwe6FyS5zwb(OgWW8G; <;+2@zmi^P z9H(e^NLLEn{=L84obG^l5yROn5!J_-$k2q`g~#dfI9Q*ka#YM~j0wst^QOAqw2wg| z+wJs?6WPiI2C*WB!P0?uJ8~2HZCn^{8${96j}qHNl(_m);_IS_mg4Cb`t @}1^7phfJQfQPq46Z?~fje9s3wOLa4(?QSJlrqT zsgMdh->Wla&oAob@R%!X_Lk|{EzM#~lfNHb8HNY;w%7(Pg)oB T|W)~>RMf@KqA2xRBtixL Xw4PcEp}~s zsY;t(p|W3p{y(9-RLk~7P@r}rHaVg_`Tvb|Sic7UuhgF?&$p;9d(r<)c^dD>U9trq zOe>{D{KST>`)}%rG;YFlp*8kgwoUBjP3l`7B#&Hm;}=F;=NfzBX%5q-3e>J&Uw$z_ zeiBQ|@AQt%@599ieb)Z|8OC?6L7J-iX=tvcg#DXZ2rPkFC|Z)6i17V=L&krt?r3 z-N_odkHykWh^te4^@~w=m+BMPR`PGzu0>Fr)~$R4{89tjNdB$;d|CyzY!TG1t*_MJ zPZ8gJ>@xeeIe`mq8k$+UYJ U8%Ga~~`D zP;vAA>W RyHVkv3~yd~E;bJC^-2!tvuoMd-fPnx*Bl2Hd>p^L10+ z%~7oV)zoRu*8ByZw8(qx_s=>i9{PFGhe-$jtiJm;?Uu|Np7m+rt7z#T;Mb~ko4~g1 zq#$oUpSi=`@fgSL=QCf0Pm2J*mi}!5TeX&g__S?1?=W|`JIp&S!|?Bm Ww*FF}Pix ffmiocUXESM94+ile{84*B(%PifHj72ix~yH9c8 zgBZ>G2LrA>_uGWAKh<2Xm{pcu_(QKr^KW?T)aN Gt{?ls0Zo7+ zz1I(?=I3Z{cq`=dvRQ?b6gy%U-?(A$>#rHscX?{qPG7}<3t1l@8yCE8(OplPy1lqZ zv2*>68@l-Jcq(@AYlhbs53@gBIJo62;qNW!@SW I#Y1gg>l^$-Sy(J z0g8wYw@w~1FZz#*dmUfAv(loNG`x-9xLE1h=t03ZXa{suOlui;IQ_s=FP(V)R6?iu zS1EQr_4+X1E{oS2UK<>H kXK}~2ox?*F{l0(Yg{e2?`)xnD;>-iL#3^Qs+5FM6muwGzHsa2{ zLA8~N{Oz~A^1{gnR-E~Ms$X2cn-sRsYBzs0BIwS(W0pN!IpZaT=G>FR%9gahz3)rg zf12~zTE+EIFG?eKUG?U(dArA?4cstt+LR bhJ#&lRrU+|k7P z&2>5}R$Z(s`5yeI4p0u9&FieGG!QPiDDyY00JMDkUt9rzJ?Xd|`V2E5=zWcncH-mI zUCy)c!pMiic^howC|HY7xsi&syx&cyl(#U?BNe-N%L%r#g104fj?lr3BT`X?2c~ D78|1zLkGJ}f53-Hr7RiZ45GN#6uuBw5a-PJ4lYHQS=LFXaP((N^xk+&0=i!3! za>>Pcq+Sb;5D6+Qu|lLQ5ljfL%!IafL;09YriesRv;@hiQ^AQ1Ls(tSOU}_61jfa( zT3t0SIcLTpEIyuL4Qn19A(aEBSR#>|63QdjB^rpc3}8VaVoXa!8=$D1e*okkOF^P^ zaK!CC-WI_@Y&h%b LGB^cHhPfnePvEKZ2z^`lU_T#h zt_u@HDLQlvL~+nzjTaoJubh;z0%l?{On)$lNej^KoJYY)!C^YbTz6>oh`{byP0uMh zb~vacI3lMV45P>kSR~OIkpK*`&lS|rp?C`@O4I<#dR~5tWSpDJO }@V=4J$Rw*l&XTMlOWX>*Map{+B vsY8&TtHUqsya zY`V@`dN;mE3fc7T5MEY){9fT@D~~@cylm3(4&gmT? UAh+bT76f zp$REWYIT=7bm?@FHBw<6lfwyGhi70A$^un^VFh){fk9~Th696W4_*TU6j0($F)#>L zsMV>XsBsHKqo9_;P?30skEp&+3YC5W5g-RJ)6~E~RRE!ZM%}<*M^brUkaco{fdM@+ zafcu2i2;gw{V0HSo4T ~_Q&69f6%(XC?YHi zXaHK6Vzd@&dJ}sso4y!vXud%M!TEy@RWU|#IEB=79FNe|;N(%$a5P3LVR#^ndgQVV z522fwqM_kI^^BKaphpt~mNAH4hT&1b5?o|tVGjty<2VRl>;t`%^wEGSpfOS|hP?k# z!vo_826b@jF+8a5h21U2Txd3N2a83BRxaFM8XhR2Fbt`)I3UVKz){Ls7y)8NLnA<# zY{Kji3X|p|m8cjyIfTOo!wVCn6YB3gS-lKXN9cE9>agA#nmVK+8y;fFY0B`xh=VZ? zd~+x*!23RNhY0}K6BS0@X>YKASOOCx!fsq?HFNqR3{XaZg!#S@RvJ?xIa8bz_QyAF z`-4Mze3=-|h5aFq@xs!OZ3SUoY!)NGuu8TGuaMnE(quD>-y;Iq$(#&h+l@ag!UQv{ zPT5+gMJXE{tP4>;?vmm54~7W~;vjsDjib%N%8}U+UWdqBcxhd`j3ai>_%addWgP7w z@yjrd*hd-~N3iwc&x!J|8@Y@lh?eFLV%Im0_SP9kFr84{w7+G`_6&VcKPF)u(V8$F zqf?5Y4fN22GCf`)Ek050GL=dlhvAh6O+NIpm&B~m2P92m)=0C6Mkbm;-K=pxM)s3@ zfVXe1>9A+kD2$5+_7bNA)QK=J(2r`75_Tcy;*g!!L|RNFnzWY$D{gy bB7$P2f ziE&PN>?O|>w!uVtHJM=}^Vmyh=PrAR4Xx2cnl{ijVK1R_XgRrTZWIV@pEi@cq`(J^ z0mBIe6K#}DUbfmAOr(XqbPlQOCej{zNuJF&wwIU@I~(mJ_C(rEjqN4aKaXB7UOe~4 z37}&6#TWdT1`03U*ujq%b#SL>rc3jVVf{ONx|9;hwHnUYB`;fwUQWT;;N>xkN)VG5 zIDasVBU(~6#V6!bz*}0}SW7?@PP7Y(;h-(z0j*xFHsF>$ED6n@rX_CK{7qugCX87z zUF0Bz31yU&qJWcx#+;bX$SFFm_at$_Vhi3 ;r?DzNJ&C7c=_6${*BX+b z+)#s}q^SI32*Oy6*l`-VGh^w-$I)F7?^<4
=s3UlS;0lwqBf 1MJx=WA)+B_ZlpDMf>$votDQ@4WfP zb=K$wQb@8!3V8zhDI^-3BSMVW8xis{yAG~zWy;qNm&?~LWz7`wL$-YXtI$i `4SA&-BKK3v=U20R$Z=@; zVE2($su2S;=ZZD*C{0VQMjom;4XlyJYZ@mt7_^b9ir$B*Bx@3>RKNmHKUebE;}x_I z|JKD06t5%RLut)>?HPxuHK9Kjtw#M)j&&NIDwN{|E*vj#;dp@yv>-3gf4o46yEQl1 z6-Kd12*jC9)%Apep?hR4MsFFt6GV5}M(><3hMuB#1cl+;Om8xU;XF*QF%JA5!trms zsZ;t8O3mZ((&B~Kz2~R|@2+mfApj9k49F4t%tVKjM}W;NAvl%=E3+4k!Qcm1xN9gA zyiRb93$MAJxrR2u{2UAsX|8bBFegefLW~v(vzUi#$P>j%j!hcDFxSv0%4du#*9wkx zR-+32JB |< CGfTrkb>Re*>;tGpj zNTX>cjjIdrWr$ZAEG8gLY%^(G-D7M0G{;4nxMtG0x=w*tmpMqkx1wR5@zfB 5 z&~NZV6G=|&_M_h@LOeQ1Heu@;Og7FYlWW{tM2#xI-JH_1VY%zm){uQEV Cf5k1cOY@!#F@ AM9$v%4Y{KHn8zFpyK~Ujk%z`{1obVKC!6%cL{8rH zPZa)Dc}Q~tOm*rK& 1 ziKh7kObKC5i0Q( @9_e&ch|_Bc z2S>-lE~#40(=PEdU6{A{=|k5vZKvq*eFmz=%T9@bnka86+KIQy|NDj%Z>pCukcdWK zKh6KQ_Q`F{*(bBT?33}beZoEuJ4Dzg6QEO^F8jm?odvt4k$uu&VN7)Pc*xyl(5m=h zh_FciYoCaPzyH7NlW&@{2ma}nN9{i%4&zF^+|;hjKI!_m@FPZ`Snv=hc(?-l QZWJ{Cw12jclc}Q~(2JnB*K6#```lR{K*(WF7 zxP*P;vcKze*Hq2~ymXgj!YtA?ap80YV<+sX)6Ll{%!>93j2<$aj054bFgcX6TFa(S zi=_jwX6kMFVBJDCeJ<8FeeRV>$?qoSAlCmH)2E(Y=)Uol+64HwXxF+`OP{tufqn`J zA3C&a Gs6gv!+a&+RS$h(Q{_ap7o$=;OY+& y zA63PgW T-~TVP3Gs}x7a&BSGho{*egHp$av%`A$`r{H_Gl^MCnF+M( 2!os`IR*^kZvQmS684>Ex?O>Pq4kBX3X8 zId>_6GJ>EF+7#^%45;fDt{U->g3r}g=p)NT;4rd?B#OkR|AL`<@&@DX@)diRuh_eM z#eY@4rsP}9@)diRuTcdt0+}oJD&HkYA!egWV;D)4G*dq49~2R?kSoi(1|dki#KcNc zHqxU$;agW%haEh1$fuVOI^Mq)a;s-#;rh#~OA$9)xX6$7`qt%VftZ)5V-hVAq>J3Y z=8WGnZlI5wkp~6ukq7Gk67slIC4q%vA&+8jd0e9I$#k(`y4Xt|mzf3RafvoT(j|g4 zjs z%6KV%$kX~zf5BH9%s8^e)CoUGDFsg?u}MlPcsdErP)WgaNz@c31 oa0P>;mz=RMDvw_{TMIQE~hJEH+_j(a_Eq*1l@mFF5oA^VOFh-QDI>4y}t zqY_QX!;0|LwJ08RfO`F>TGUJrQLz5+Y){tnSYDQtzV0@aiTt2hwjxi~CcOAH>3j+n z-h-@IT#r#YxSpX9aIF<>jq8NjqQTL(-qn2TTHRX6!`14fMRI#%%lk#NDR;~JL)@|D zof1BG%ez`6X3I0c7bPiU;?R&p+uMk(%Pywf9A7@Wf>TEi&~!_@QTwDl?cF|0*gjF{ zt&Y=RG1Gn>&Xs78zoURp bXurGV zE 3idZHKe4;IrBN7!F+B;P&czEL9Cd>Ob+8 ZPa}@z4h)sMO(DC{eCNzX!(>e7Tq8s zNW~DEKhSDuuhN@y%G-FFN2a&5C@5KC>194i$*!srb%bcBBh;6xp33p_uHp~zUG`wJ z6;{WGCoX(0!;QQ-=3{TLi=Nh!?wZK^)@5w8j#Dvbrr#rG^KB61n$6QR|94cZi=Ru^ zudt8Ot9_D3gIT4dywUPJxo@hLHq@^rH`mfAesNO(jUPW+@LRZGF3w{t=Jp%6) zRnVcj^n(>zDfGM15ErgL&%`xa3Qb=iUk5J5&9G%~#(e^3%1Ss7uY;4g8_wf)wlA7X zKG7CU5HiiT%)%w5gIddNY~o8Pd~1Tw;iMGoxe+c#ANb?&LxjQ;ueWM^^w!uGIPGmi zxIG+E@40EWFG79sd_3QA-*eM`Uum7eywzxp^+O `=xdmsQ3!UPwj z48f)HvwZQa51v6#k%hZmBn-SMMQBSonoM{+lqMfk&^aqOs<3~JANHh~7$A&TCEZaf zx|71_&d|^`#iEbJiHvFdJ9DIv4r24=MhR!7DH*g~3PC;5S0sg?cIZ3I1DG`QIXM8) zKu__fGl==5jATA36PZuSbmo&X7x~PKqu{0VH8n7!({uc#jFYGgt+ikN0IMN*Mb&Pb z-Oq6|?Blvd4*3Qm6Kwj7W68Een>k&=2Do_M?m^XKEoM3W`%w9r^f*}eL1-O9@|;)~ zo yl&Q@5;Rg zl8Xv$GIyxSOXuDP$wi%4kRqquk%gYzOaCf&1CqC7$|iG%inw&{*=tqOqV?#Fd@f6$K3VYeHCf^?K9Q@M zKDn0ZYf`+Lq=)(JvZ=>j5%stR`%hW)NurbG(>p`-vfcCwd&*f#?|jkE?DVF@qCT*W z2IuVMvk9#}IL WM+$fAS7<+oYb3r+q^cnc}Q zxF0sMh w3ZK|0^_j4oB8xWAj>e#}O<2Y76befko 8E3&Svx~zt zDp!=+aR@ X-;%B>}l& zYH~F$uq0$=$+pZa*`6C#{&0hwFOfg9#>QcH)*?sd@5%;6ZjsPMFyqcKsjXlJw+Gn< zU@eC8%o!*m?Czwc4LJhGv{H!Md`Xwhm=Iycz??~!&6yBk&hTC~YeEJt#Z7 Wkjp(zmC0L2s%?2a4Y?36;S^eoQCe)NuQ*i66uy9KI3W?cA%5 zZ+?Z!KtB_Q8F?b|bD2gFcOyx|i2RtQQN+?FBJxw4MiF@~kxY@F-z4nwaJG(|iO+7% zaUGI22UgF9Vl`=HJbD;RjgBhJcO<0|hw0-`^OgZPA+5Y)lkJ~NNK`4=@StxA9U#;+ zd}y^8C-4BO8n }$M6b`MkKgOc)- fwg=Zu!4hUfz z#Xal9i=el(NDh=vGaA1MGx4M@nTlWwFQ5}j1rZll;=_1EuH%ZY&Z33zglyP6l+3>h zB#VZt6%>xUFCaurx+06%I3OTQyjY7xEEz6hViZ{f>zx$N(LR@gM=r+@u>x8;8-p~K z9_N)}ys8j=DE&AVnnfRq3eDG<9kAB1_J9`U%*WV_J@>~k%<&eluB&0Gy@TGy+fpgx zpv!B=6AixCbw#X@GRDG-UgEV-%Pz9Du3KTCMUO&9iDfwL5_B!O6FF<`3lDIvJBptO zn09fr;ShrXjbj}Lwu!JfN1YL4ofzieU`)2oi*@uvzI@-)iAxNKw6&>OIjnu}rEx}M zPOxaM=4y!JV#5&|^f(BECYL0zk~FHo-Wfky;ZC`oA)L))7zPEE mU*^pFBBsgW6pcGIuE*@1xuumdXVG@jCytpB zVie*Qa`ea$`Dn`h<$Uz%`=q2X2J;3fD?rK`Q~}~XOCP9M+76NMg15{k`LH4M5405w zJd$!`h0KHxXXdbKntcZ$q6Lqva4mIWH2QzSOlZnNs9zud@7N1C`@Aa0YyY|~H0Li& zi0p3v;U>i3=1quPHB5-B>en^zsM(+bo3$T`>)Q{-_3ek^=IsaW=4irxz(xY7a*zGM zJp~^70WX&E)XRP-ZfHLgbF?ZJww0ItP`B}*Df{6PFQ2{bhfBQF=E{AO_5(=QvmfxH zox{A_e&AM%$YM_4 <1LNp8en{ah?5u;zCSy_5*iZc e~-){_sF%%Vj?l*S8;Jw(9H$H&-tE!NXOZ z{ZO~%#A82{G_oH`Wc#6{zWq=l+YcqO{ZR6E*bgqNfkwnNc&jU{2HA+%;I Fv?+TdaCZZHgGr0LjgG833SRaB zhVBOTh8G=SZ;(zgc)}dMZnJ?fI%c`qp*= >P)M0#n6*=^)x+jZGCAkc~~kCTVhM z eI7*^tOY3mHqJL!Oa|_(HNBWJMc0B#7x`(z@A3?6&sb1&S;JE zF ~fu=V#A=5bfUXb_dPh1)0 zq9_*$!Gm~VlK7MSvSb|!AeiL>TeKjXG!YlEeZwyy5nk@ySSm-uG6Z%T*dGIZcHEjK zGAVx)ea=*E?}b+`?t+(3t79G>kPr)VcJ8$y8LVE4zF*!~M8orA_Fb-L7re44zd<~= z|Ll5p(d(I5{Y9mnMb)H4^H7hJ-pzEN%YE~X{SIhXT&6?g!h0*RY!?eJS{Oso%<$$a zp5AzP<+dh;W~fskECk4Hjsckn;Ag!QHg>>!9hNf_hvnc`5v{6vD}dh8YV3!|oaZVZ zz`FX)l-_=Nd)!9vNj}=e`#ur$@^C}>=xM5aoMDGsaR9m4hQOmJ QfiUl8+m~kuKLTq9AycYRo zStG2Wn1yi5eVUYC gC zfZ%zH%)NNXBFH>9IDdul>M<;HctkV|5)dqbmt9Tf{~tF#^+xAxZhU&qYkcCy9M2fF z^-9O6*{<))Ih=@rL<}Qh0D khQu;{q1uD{(F{jb6J=N*?)!hLReuevT{ z|J-Xl{HucS`G5Er*{ptTVtj-maSe^|xkg3!TmvF}uCWk4>hCKU0UIx2Kq1$Sfb!x( z-3VBGPS$IP!V&Ni>l#Ag)s28&UrE$82kP&}nInwj-{1N@h)|{O@vo%b_*WvvzY_7v zzl3Y+ktJgME4iHU4{~n0Qdl1pfU23Xu2HdNsWu@{A;xCj&ow1Kui>Oo<#@)wCi8cX ze =yLQo}w48molhdAx?g<05z;kFk!-?EN|^;kJzKgsgP?jjZKyED!u$&Uc;@VpdvP^ zyC+M^M7%7_Q6^slS!4t2v5{7Z>sFw^F$e7E@ZNbzRpkfMGk;Qo6 uuhspjXCi*-c*!7}@pKzAIw2>J=q7=MjaPOfc zFOEZjp5+t#-m1KRas|1;i}}Cd*Gch~J-SZBvC>m#F=~TnQ3ls$AJbkj=0U%k4!|dG zojwYm*y&?B4WHQPW2%X=w7q!m5U^GqFFn-~! QKjJWcOi0f7IMz_lsO_;Q36J>iP^3hyb5n_|( zToL)WD NzAle+k9iE4CVC1E?qN7LxkZ-P7@izP_X!OK zv&a&0nmtJxW6>)zX&pm3UCB1LbLf{3#GzxxyT1I9mJG$ab!>maccuDFXwi7-*?5?s z?*5C;303kXtYF01hP$}=Q?YEZ); 7r8sP; uoXIp|;+>e@!huG*SvFJGa6I8UL%<(KN6jbNMqS zP8dMal+WO7I_n(^z=L;-k%IB#u~293X$xxT@|7v)Gfc47-YwT_3A#zKp=d6V!FL8L z&0rI+04OaCR6<}B7SwK>v}8KeC&h+(i TiCS{;$>n~Gu+mc>pv7@v9Cfx=4&nSp z-k=w9Kn|%8A*L|IIVy+TKuXH_8ibq%YWTI$L#sU%&OKRITH)YY172}}7xy7Kq4vv^ zj$mXY#7zF07+~v<{3S4NMzO(dbL Q{b~CVuk<@T`5|N1=FG-@Kr-RCA$a^KwoG*L zDgYT|HhdTj>jk^yq~zA@;pk%JT{ERnZp(6RmCYA(6)YH?^)59n8hp9XLG4<`lz*KV zi;eORk#pG*GeQ0uIlZi591BJ-t9H0&wGuy0&d@l7;)qIm aS7GRFn~yE{N{&d3!y_|%aDf$=8z`I0%ulx#^#l8Y252Ho z7$ZObeTcTqDn+0|*qWmbM%~=8L&OjTz7 &jX^xFU!9Q%Eow?uP$sx*BZ9J8eG{;>r8+`0}h)=O@Xd%B( zpx4j-i90mVUgY*F?Ju~!D*IZuS7Tr3_NLmufmc*Pm=3I5i#yZ-N^|>6QB9_996#lH zkY(>Hp6^s54!$>vR6qkn7D1`Va_8Ivrbpsg%Du5?HGT X1j z)fS<7n!F`lCvw??uN|G(IfK0UO9|wpe6cxJ8J^u>`jZLWgKv#UHXFdYcm0iI9!2Hq zzbHLxN?KEj19@_Bc}85;*Gi*o)M)PKA!Uq$jQ6^tp;essgCC_>KLXjXf7#q^W?S9b zffTY^T4RS*7b30MJ4gy1EecTntt)%bXTV?Qzh*!9+b*ry5B_jj|CuyuOI;>zVr+jr zqpNkRzySZ2e!eYy6#l3^H{hkBt@qdP{H%Hp5ncX8cmAro4E{d6H%QML*Wun9?jQTs z&rO;(n@48dK1km~)qm)i*x`d?w8mko$&ae~JQyFJVA7c4r_D`FoZZuuFr%+qy?T}G z^1N4eewMVT6wKqKtYu)^>nJTLeI3Ge)-d *|t*SK;e8u%_}Q zsdy=BnTeyq;&5abe_14c_hlRzR=$8TYl2@Pe7$8Ex49Us9mgK8tu^%CmY~;LGz)P5 zmg~N^i!lVX$2atdl6Z@*iC=1WOVJ~)fA^2J$6mZ&!8ridWO6lcX(wqXYA0yNPZ&38 zV&!Vrv1FHh$+bOZ8m5Ac?w_~Ec`)xW)1#?*k6hTA7kXiDUe^oz^RBs2mKS{CWUlrR z=bw4i_z0kLd62s;@|w09eBc8l+AE|rrZmrdVMDGq)VV8H+tpcuu#~{u1KOx^Wv=!b z=aIa#$|0G&uk 3*o*1K-W?H6A7C*nt`J2S8#SmdtD?H5|O%P2)w%pINEuWMlmz0+fJ z`(0Cb#9*GS4$18oT 5Ln5E3^*Q&6JxvT&eCrD|tLMFZ5DRSF^C?J_ $Ops)T?l_4$?8>gWV1n5(AiQ)wvvhW$YcelIuO6eK!;3R@T_6LdO3@k zb^J%x@vlK<` EX>afm(p%sWQy7D{2G?Z VH-fR=1E` q4mIR z(c!!;P<4}oi=27uFI0osqRKqmg}r)wdqNGF&@XR6@Sx?nTI#|D!6R4XJ!;B_XJR%y zMet0|hvzUnb4%fI!ZWV|p31yxe8U>z$j4DOvfiP#a?M}O3)O)-GY;Ly$@Dt&pE~nS z1HKtEuQynt)FI;gK4bV+uE$A>XlZ+Igwu}VRydB`Qs{~haakFO3%0nW>g4NEKE-_| zKW@Zz*#h|*w-h%sm%)ku1kQq$d1vPf>0wAo)mlae;Ww4`!4fxEA4%2zo{DYyx!T7} z*u{7-mC<}%M;aKddW~gPeD37lifm)o8hzIz#?Y#SAbncx|6%Xl z(=^4xLeqlE5=&)s)m>eX@}A~h)VzSVBC`~M^0aQ*J2Shh-K@;4EVYY)fOtzyGf^v1 z({@DzFDaVJ@A;aUbIzRQfMD;>@ALWN+auZ8**UM*YhJf`&CF}&STG(;tLbWe34NcP z=iz2o~5!<3t@v=w%H=b!pT*!2( zLLL`-I)bsZPlU@e3Dk!^i9MqcOY)`^Z&Sxs3RJW~xpnc+Y+!C#{3{!rn_$$&0G$_B ze1b;ayxPTQ<^8s+I0aj3=mY$1Qns=mXY3-`HjCsod8qg{?kNuyC&T#QgYym*LsHBP zM-=yf@xl3`Za^G2!$Yt@ZeuaOZ6nj$L)P{?9{8=0jOsWysMN4}B%V+bPXzuXMdQ!p zIQ+qCC#=yp%P@64<(*C*u|@ScdBhvl<>Y}u% 0B%R5E(E(mdd3Ue oCniLsv#rLgfL*nAqCIf~D-d8deN*)x*|hjNY)>nQZgiM8$wA=%`FLd+Ws-+je< zS>6AsA;^FI21YIaw?Y1bGOZCB{-bHA{2yO&iRjTaK4Mbx=ou5oL?k6ojY#cq@34FC z`JY&aF`3`TrHaYD(>*05-aV*8H+KVZOZn6(Vp1 uZPBey)T{zTYMNqh{83sTc-HbV4pO He4MW^zM6ES}JUvnIu73?HH%r2L^`y7Y6`@DI0eYZC-9(V2G!{ts`kaai^ z$QKi<<~59T)!sR8zazT{Ew(VWDBZyH?n|hZ(v2W2XD$0i2DobPHUd-PA)16$tm&ik z0CcTrTEhftD4=F>s0#pflW1Dg1ZrbI-N~UY2Gj!4G|&WU1fUjjs7nCVE1CwGK*g*l zp@c(SPTUtwgH51DyWGJX=qdnZS~9P1WGxe*9RM_x16>QCjRnvU6QFS*HG)IU0@Ntc zw6+OU2cSlCsG9(_gJ^o43DiVDjpI-;FX-+NO|LhBIuKA3In)9`9VnXCF@ZW9P=|A< zg@8IvG_7j_bsV6kaHw8DO%+Y+nLy>ux|N6~RzPzp@u?2w(NahH#yKuGEW~Y%4>%8p zaDNjVOxDjM#ot}e>F$p`-p4~-F__0%9m;?Z#Gnyy8rd5{=Iyk8y&tv?J;pc9X;AeR zRKv?eHiM6dkI7|f?hT2ze*Fj3Jp^9kw>hAvgX?bBZx(v?ZgMzx*9o4t6Xx67x+ZcI z2Mri)>i|CTVx!|@@&G43<`8U(K%EqzepEsIXbP(B1S2^(eFacNQhbb0fcnEd0Q&ZS zk+A=pRJjL0BL9zFP}Hhv*A4%VTg TyqK>=o ~iLix6DyXLh>Wj~BSylM7w$ zBskZ}C2+9jQ3LW-8e3Z ~{w7UgwILEKZDM&euhmz?i)M
1f{)Mt^^&_Nb*qg5)Cr zb^ySpUBaJY+TO-aa&jU5Q7S?Su5+ZO(@Kbf94f?$pGxT=dXgTX2kD_{JE`3^{9|9} zY5dA-vq8{)>Q*69g+%1ppa%C0T4&Q`c^$1sLQotL0YIDn50akj{Xa@r%5NG^e**)@ zN2^SY)0vt`r&;Mb?#9|88ik?IHHa&@-Of;U;;BBqj|Bf&GEXH!_|*aw|10_f%Fx9C z>IA#t7^h`v-P++9UWf&2{7WstqO}G89HID=h}5^%1CjLBdN|VET8~S_pA@9HwN6c; zY|QWiEMF53$mZ~GCQH7xY;dOgklHl`x$UT&8tM)@TL<}J=@*a%?ZE1FD@ud!#1r5< z;5(W}JJwnXvm)jUk6S|7x>H%s?o4|9X7a#jIxjwugWgYBycSq=auHPg2-yMbucLiG zINH&EoLT3e?DSU=T2ce#qSAp}X0-J2R~Y;DC-UIf-4Q3Pp)(UvX0P2HNG~mCH=z*{ zLbU;Zfs4aJS|UqsJ%aDt3qZ)YbT2Cbf7wqud}Ot8_{hDBLl1Fv7G)O|g{YfA$ad^# z&!=V_H66?)tW>=t4CmEY=OZ|NqC7v8lVKh3BMRsh5}hqb`J4$MA$x_I5F4n|j2faF z1$Yg#MK6d{#v>b2r9$xZw0-m!bc5qT?J(D*Z<~``*2&ZqX@Nu#yONJT3dZ80Q?Udr zn~E3Uyci!)E?}^z8rPFDCITw_5OZC`7UBn!Bz1$yup<`lP|GC1>Nao!Ks0+Wh!7P~ z9UT;-exb-Q;5+LSmxAUVfsiD_k)aQoWT@wV60c8c;z)lA3)O%~LSP>|xMt)NO@Dy@ z#Al)e 2L)*>`4WkYXX~jPh zB_YHkt}~QemOPMdr{v<=O#CSn)G?5pfo#lV{NsZr=_^MH`f2E75P_BSnnqBxWxvqv z5y@2IdaUZ6HRUMN5ok~GQ4UMhKez@w%r2Y|x`nUV#0E`8q28!sOnI)1bfX#!;FPNU zrGpRPn|UV-0r$Ti8N?$)H}ic&aNP8`=i=M~Lo1474pNW*H^w2-Q%WBUpkbB01p|A2 zJki?4a;(`+BXk)A>TwSQ{<51!#O)9GDsCFVE-fwkX@jz+z1V*e@gR01v7t~u?*c+X z{5f2fcWBzKE_pu0gjk0@nfFuD3Do=(lIJVho%hp$b`6oAj0NjG*l#$UcOiKLtxns% z!FG|ww*Y@M43yGP6oOrx@g;c|CKcozDlT9t9AWf#16I06AWp=_Od)>xHE$nbID~L7 z??Q1dYPaG`Ni3>MI%Dzt2z~1!3YU;uG3{4sn1hDf@MUKJ3Y*xI`= >i1mcv7i|#x$5R?(MS v7#F*P##q4B1vKBm_SAa*nfcnkZ_5&`1(7OM;2oc zLyRq`KP@DULXBQk^w=xkElWOPz^`biN}n+Zo95Ul>o7o72JF}!RyCjr<#WYg9?82; zx{#aQ1g?O#(r6!bDzYp )F!W^8cpb?xQM1+Pvjxj{TA5jtJsnO+7!x%W~r~yAS zGr k&dZ*5Foc5{Um2L4MleiOE;9~5KmbGjgM+!bGB8;NFn93|nGy3V z*ivDiRvDm7jqnEpVrWu?S>h{|fmvb%gTxqOZ+U8_KU^Ut3ydHV{XtxOW@ai;Qto^Y zQwcM$5 UA)r2 zVmGsR%$P0K%!QO7LPHYLHNseG)&E$?WYBN_{c9+V{NDzQl &cnjXJ#MdQg$ zqU)b-usZDV&<^1j8-Ca5nH%Cd%#F>xKe(f#%k!PLJy5Sx;=TX5@8|Z7?im>Kdi1Y% zHNSg! 0&_ zAFnfg`-}z8JUqMM)4$AG{Ny94&1U}o)RKuKCq*V7o4kC&V~@2R_t*GUBd3nKXT-VT zYsbtS{Xo){v0089PJ6Fsdv5A|U4qN?Lj2C|^?UT~w#Z&!yD@IagKu{&?0a*+(E~o{ z@9op(;faYWA1Zm|uEEm>eLb-B(T+oA4P8IXa%e+g#^LTqLXK_ubN rZc=02zHGAXt z*Oz!k&X0WI*o(`bf6U!h`D@;)^r>_2nR6~}ZN|*Z2cEm~Y}Q9LmfJsm_LEI3ulvmP z=?g1%epvs5zV9u1zhKFYONYGs_OimSZeBh5>krm=zi9L2#Lri*D*5)VZ>F#PdR^&v z9lxLT!}|3-9JW4=e~}|m&HM-CI{2SGndWqO=J1_vTt4S_&VuqAt8msrWJ&x_AAA_F z?O)|Om>r5R-@otI$JW=@AIT5OWk>emV29^{M(%DF_oIRC_z-u$Iu7gvWV{xF(`rhV zP1$Etw%e4wc6TG2vfCE3^+8WYXzF{9?X|T$V0ZTqw8iYU2NYyy)v_sD1Mn`W0}px@ z*6+V2-)n1`gWp5&ggsz?_J%q( *vWL)Zhh3jCj0XZ_9bH#EiHX5(``IR3___#1SO zUe571GsPe9O83hge}pOig2i*jbNrE}_&+;ucXRwvruaV}jLQmG|J$14U%z*V!tqC& z;=eg;!32)~9#i};PgxB5iT@o;@xRdZ$qzXG2Tbv|3|Z~u_#s~d|DgiP&$jcsZ7qK# zMHtzt`8nROaktvPLIvyrg*Ihdc2*E{fm-E~I>db$m&M6#%UVI=hi%HQ*&Av@BdFES zq!bT5GW1qPbR*UXXb;(x-0bXnPz!3bjWpxq{I7kS@`kJxl A%FbyB< OTDt)*35~r(KC3Nkpkvx{u z6 f@Lu1Z<2YgR{y0;<`(ZuD348U&x$&7d*duVT h>3iz+tS;6?qh5Kq*o36In!hM0f z{i4Z+>wubz_`_QkeqyUl%7GC8MD7z!vaR^a1s%KO lZ7kAE} z!DBcrV6QnX|JhdOMNSKZWKPS|XK_~^qlIzYjFz7s&B)}mK$p#F>HW;|WKIj%Zca<# zv9?z@EfBalEe*Q{yvu2UrkV3)X;?@Crv*B0PRrb9`kd#qz`D$7X@9!o_na0ef;lbU z?%Lai(*hMVr{$&H&lWLSpfTl1u?J+?1AzXNJ>ViTv*IW)nL8GzRgoO1JQ|3+Kc!zC zm-~T8mwS37(yj2SJ%@WpWMhXLZ`;FBpQv(ajg`fdScNRYN+%tbP0$a=u7l>%;n@3e zt{m{jA%+}S!q4iaARi*NqO-app_ZlB{!a&v%{NpvaI_7u_3uBRUmvW?cW}6azW*u_ z>8?1KMen)J$B8VOx8b6Z=}leAc9)XxP bX2P{pC>hJC)LS -_MA+QLSa>}WISr@#eI1H}j zIWo^p56?U~1Aqb5p#;aAED8eX+7^c!+YgSIL*9dJx>yvv@hG>$;75lI#WCPvIU4 I_1bs#7nsGq@2GwiAu`jbe1;^ o1pAUA2SNm)8>{FI*;4PF}C!S_>wxeoS7T1THTjtbcy= zlV+fwG;iE_wN46YhVdTC(%dHZ&*3M`mS1YR8Ap<2YZkI3I~3Ih_c2oe$4G4VSS zCMdA~Uy@}gJ*GbTUW`b5*CY9#_elluw9BFjjD522~UBrfI zo1eUI`a GWz}X>(olEFO}-oxGFe__G%xls#pigcg9??>Z_eGn7*GrwbT3X zU(=^`>#ib?((+v}mq+hgsw0mPpH@vC)v))s)vjDsEPbo$k)OJ`I`-~aq4nv%CZ8s_ z>!i|nsWEP+_w!xm^7*V~#qvoz;XtyI-w?b06VaWF$T<8;qpTP{^>`_|h dOAT|qW4`7+v}S(7e?`#b(SPo) z{Y>}s+uR9(@~ )l-M@HM$jz(LTatK=UZ4B2M z<$S!=eo2a!DYRbymCDxN!8cMa H@Hrs(b)Y)3JpCD>3*EBndq!57)qKCTOMsT~GNL`IkZS$0O@M@t!1LHjj6x4#h z+od+%*9C89+=m7sCJin?tbXnY@MW7NLqM-0c(2lPqq()x ;=avf2Q zLir|9jv&_-&Dw_FfI63_t}QvyvJQf`*w)ftZ%f==#^#^CQ;ZClKE2i=CJP51>E)f6 z;CXB{7-~oC2J76d_%SK{f^F(k3J&E1*NHcE8s>>I(eRomM^GOfad=E3=6U=GVpdN! z60`6ihzUh6i^dY-fK7OdbPdks{s1`yPx b#2%Mp&&*;A)E(l6AQI*AMbx45_>6Tm2! za@u>3%FcCQ=d8#CXL?!O6WwWrDKWnFF{T7KFEjNGM`qddCP{Xsiz7Y1hAZHRL%9HH z#4DxV4Q;zv7>lhLQz5AMfJ-jrR^M0@+0(v9QS!5gJKr iYqp0ku>4a^duXxLgnw7Fhhq3wX?*Cxm=knt zk nf{jHBQK)cy_z^#*T?oK7x;8>w zCy8_atGZP_p;Ey{2&}GJXoL{tjN|0|XPl9osrxFmh>m#5cbz;!eE!K*EutQlr>ZbQ z^awX7kZci^+C%SmOypTDd-&xQU5sH81Md~}OpLKyjM1or7#|sH F^&-mMk^LHoez~75wjQrh?l{TZlY22V<{#G>puKq|UtIFT> zGP? z{0$CHX#OTa5@#!kidrfa+~434@;8E<(w7)HH~W!OgUG2kBHe im=DYcIZL!q027kNz*}jBO~ikr+Ek)HC}) zwmjx`;bTz;#=bPd$k-{EU|+*{449gI{2+<<1yLp{{(M1*m)i#(A^Q;I6yQgYQ^Sv( z!#`DOA0Oi>qkXi*I~n*|PK7hJXRzc|1^ZZ2_uA~^hgVJHSuOkME*M22e!j2?V&DfJ z$v(PrF)l$J__k%3QH&pBrPerJc _b@AzXtoj3Y2Caagvo MlUIE9}rvh?4uYf zL;uD;dcSPK&ug=fM#unC2j)h?*$`vfiF#%qEbH47ej8mS=BfM8U?XE=wq1*Tq+wR4 z*+-USr3*xvsQ7T6Y#(#*2-$}qXEA;RIU61^lCx}UrS|a{o-*3Up|`GTADyxCRR#Na zqLx9Ss%#Uow$rwd^BUFp7;I!d8fZNzY04;pA>^IO@Q+=LZ@2_85|EjP|iS*wCFS z*@s 1ewXKXFhfw3_I zjf@T3d@c4tH)v}1VH221y!9k1o)gsZ6+nv^KLk0c_z~nR8(<`7F0#fd89#dBDWiR4 zzkXHwXpWVzD%i(>K!ZY6**=E7U?R_I*~hUAU5veiYa=ms7xm1|_2OcTLml|`Sbrnm zdglAv$Gn<`?o`P>B2{MU_CaE+o_&0Vb)$b{A6AbEKd;R`ewnK?wyAJJ#MqOfo-sCo zGxkr^fw5uzjEp^%cP;i|#oL24`}je)IkJyeM46} EmYQQJ>-TsCybU>_B&=l=1sV5e#y)!t8?YSoOR zvi(#&f2I8X^p0o$EkFA|t@HDoaMHxjuV?Xk#?L{VpQ}&@e(p~&^79+y9~=2u`cL)w zx$r;#mY+A*s}esA>wlRh{JS>)(*G%)kv|G&O^lo*>KP*+;fx%MIxzC3cq1dHtpAt( zB}E!MoDvPbnL?Nb|00Olj~_uypdT@Rd|#=5S%IgF{^gbhSM@J*untxQ|FZ6aL4D-W z_bU5EKct(;vs(V8yI>UsFcar+wviY=cv7;55nPN*PzTO!>17n-$4GZJ+Jm>$&>@3= zsc1hg<3&Gv!1_3@p{d^ZYrS4GiEG-AOT-%agt;0|9i%@rJh7ano7st)9Gq1VCu+us zY?wG~5ZbH@^NU7;ah+3jyGX>KL~9(x(0-zx*~Mti(B7y6L!azvWauE|E;EMuDESb; ztr<&Eh{@^}Q- 5=c4pKJ5`~wc{KQ zOJa 65}DU`uO9L?vCNC{sVPjbv>t%)qkz?*WI?~47xiM^4^C$BkNqs1|Wu8=jL#! zOn)RPy^CdfUtk<0jVe!xGEp+zgpx-%B^eGQC8LRw>#+sxo5J?|)$M8~t^M9H^SK
l&nFInqlLpXf+~I^~Z!T5sWsL78oLpbhO>- 2ELFim$<5E{Ji2qO=dis7I7oOendS%qWTUqa=_hsn}kEo;9%-O8UPxdwG0E zS=qm^mww^|L;8tvNFjG;MDBzOv4&&1N4f+Xs}%0|=-1hYJXiFk=$(6gfKKzWWRQwI2aiP0~KGBH{$GwT`vq`opn z!JCm5pm8Th }`Q;}R{_7I7B5X%i#P=*SN2oxTn{%W#edx^+SLgWP7G?oP)o?nIKaKzSBF z>L{nLMN;@^rAGuEug%0`082bj#HdO r z+>PI%_TP4a+Cy2?O#D|@L7Pf&?t7dF4 +u(itTWcfdGs z7#zZ;JDMHtg^^kKbh%eVu4UinN3LSuzl&VXz6Wi1To`ypWHkK(@>3j2-^g*qx#6T0 z1L Kvtb5UyNT%PvN67i@<=Q^!oW8*B&?2mZoR-%r^z|QC#1gduKo6o>> z;w#;`htzj(?HV;KN 4b9#cK7N?It7v+OotK_pR&K3Qes1NhZ72)p(Wa}a9+t5 zdil2)^hhRui89GVjf{xwv!b44vO^5xB$ErGp2=h#Q1})Rb38%csbf9wAf4Z*73j~1 zWKa0dcnm!Yq@JO@+OtCB2>%ddsb%p^Cx&`5BIi(h<&<4H?9D-X46`Sd?oI+X(S5Hd z6Wxyq)I|3KqMqo!L899x>KRer1Bnkw{}8&s2s}aVL`PB3H@Frr&ILm!+0)NlCsZB5 z;VUpPnlv|xq#i+e`;RmtCd2SmLKjZu%1wlNM41TPD~KRMUl#R5=wwk(3|=hi8KHSZ zXnP`5Ii(!8DFxji{eO@aVL;bPLf2eEmnor(l+cxlGNEfN$_(9RLbs750ArX#pP9QH zo||@{&2-$^@F3n>w!!7V3!aK<)8Cj~-D0x5Z7)MR-3cA|x90p?9RC)|z6n^6?mU7C z-bJ84c(z;2X0N9v*HAi=uQ9NX7zl}hE>R{1?iAD#10U7~OJxX~@D3FftSi3|CQtYf z)Ism&-*42raVr8~E@%SE=QCrbIp#7iM#@r0S}yqrgCDW3;j}B=2ZHnC(cKV-XCdp3 zw;7jyUyb-hVvHo>Ule5`{yvHLS42Gy{&M1%$^+{G5a#Pl-W`a%?#4{LQZ)qaB?Na! z2ucJ4h&%WABZ!j`9OB&Rj5=^ 7id+mgB)EZs@wx29PZvB z-d#7qP3EYBqBV0bT7%t;JsO#eK9#WBIAOPP!uk_oL3l_U_qq#&<<}ydzWE>oT+T;B z(r1jF)J+vkC Z7j`psI>Okwe+17L$zKK4MF-LPJM{^fPGm_9qeeMi2?jA!tjsT_i zko3|Z>wGo{d4fimLyG}4-vzJ;-0@7(-QcsLOos50;2H^HzNj~HaNrH-7!4ovj$vrZ zD`*8p&AZRYgQd&C18VAn4MH@;WG4B6;gVCy0h&L}1trc)+{?iy5qQ!tA@2tP-nWbx zxX=4+-5Qn*=$D6gtpa!=Z}HefMs6cNc&9%k{Q&@r&&B!`G|8MSkyiZ<2eac|qewmh zm~U%SugNfR9PSo$aJ)IB9%$kFmS{%sc5@(eBt4ZJsO&&IbYg-oprfKp0%|V_s7TZs z1r+@-02hrRQw(yr8x_^S8==YMc7$@q(=itfO{RqA841lFP&qXQ%rHkYgz+i-75qx_ zZ|^66q&Vw7A>84vM_UVZ%fc7Tfk)sDUMWCSIDD|U42ZKo=0K5GUoWKnA2mKSkN?#u z--{J^KJ=;~{>P8$9DG-liGy#6GO74SqRc`P(d8)Ahl?_EiGxThE|H+zZ?efoxez}R z4o{D_S%{C5x~*mdIzs&QWJjKM*-Vq$k71RaUb`pJvH(sg&LqB{09@tc``+(s@%?Ie z0UcjO@%=~Rbp|XGWnw@pi2=(+Ju#pW(Mi!0$_+)CF(8Hd?g{?{Z6WD{cN- USO+7P!tbPl~>aeE}(j2$zLIlwQ+?OqYLpGV7b zaeF7AA(B}9E6T*{{(`@xY!8Tf;`K#gQ^f1#qRe>xIcW4fP0a8F1**ak(?8L(4|&g; z@t!5#WfVsga;JIRJ{VAFnfykFXZ GAOxj;u^Po3&)fos4db^UVuMn|HCi{Snk za!(9F?C`OA=LU%~b Zx-#NuAp&l#O+c&iDI}y9!AYIv|lEdkUx!vg48f z@9Mtfst}s?#aF@NI9BPRLGCCPHqM2>C_S!zr@aT#!?yfK4x&69rn-?(=rTQZ8uG z@gz%l#z}Z~h)z<6C;Q_$B&5Z9noT{W?mFCf?>b`jii7m$PRT*~ll+?q(kHNQ1Of8F z@`pV#LJ!aXI?ol+5T36dqqFE!Q6?6}NTh!$>NOVKDwDpQsP=?^k4CWeQ+FEGYcs^B zhv#IY6rR`oO@!xU;(p;d2 Dfgyd$A&wd cg|Zs7Ph({fwAUbo z_Jm)@N$X6cNxrTTNMpWkE`Ym(MbJ*MA}5$OHpk%`8X>aJLID>wdmao>x`!$^kYj-2 zo$|&ZqglzJg^h#MG@ZE(sJAO}oHc{ej`GtevH&F(M_Tq``fx8I*J4{sZpS<4WM_+y z>sHY1I-}*-JO~dLjew+sbp@4wufw}}%P1y+0qu+uxbtlhbr;zHLUIE#h;R;~a3llK z&=16kw*Z2|f0}wSQ9!qE+Yj9?z|=Dfol%38r2I7MBC#rh%>)CkmnSqcdWpb(ta(qP zI2nUD8J@Q2i4@q60U2C@d^9Xd6A02leF8yuRtP+i=6Iqxo>L=OcU$@4x%?(35Z&1X zB7r6lvnX1v01_SO_dN%M`E+Lx7=+ocr#6o$nFFFwzL_}{v7CwtoQijDM{lK2{s~aQ z1_gxj-y-2y3gs(+XXo&ma(KP{;N1pz+(Y?z4sy^kC=8Dm3{ydI((mWtj5#3$MiN5d z8)_&Y2y`Npf7=|AgP609*aAZtP|8LVJ(O<=1Zr$>qbQSadrSZ$-xeY2;oD?&?E}C? zqi75nhxHu?ah(h@#XB;A`L|hGAphMk9m!XsOh}qaNWK;Ien=kW?A(T5;n*x~jAm7U zTw&oSSgwHgiF?r51D9w8gxR;l^FoJk9zmpdkLL$KfYTFbfFDOzw32aU-t+jdhJtch zu%E-eER0;hKHrR7LZ4v+XIaA%CtAZCL#$zOE-t~C1HLJQ$rJSJP~Jc4(;iVK#??2K zNMe|nq>kc!E5Q@!TV!iv-!8od8B (0Gy`-@G3*4`)6U8K zN&6bCvQx*XL`xF=cZan_24KQjn9nyJ#ZTqu8wasND)tj14ah5+Z^S;TtKeOtOyc@V zlu2A2M47npBJ5Y?hFWIa7zuhXoZXMM5Zbicj6&-Hx~jIHAkR0j8b(9y>o`^?%`fCx zMRP$;2k^5kxfYsLfW>FYKVaK{kwz*D(v}Qiq!ru>(xj1g1@ea3NK1~Ae8E3Hbyl$+ zUek_wj{mHpRLm-t9_PW_%Xm^-P5g7P&e1|qCXQMpjvf>BFlwS#C3Fek_*wxentums zh5wVAEFvPY{tp0Pa~ h zjbC@BNC+O25d0*%KnPOI5s +X-hcUu zACK!w^ae}xt`vMF9@jCaH;Z`OOAYjEBVvFuPSH5= +cU+ @ADV^S)Y$b82h~br3%*H51B)wxre&`UKb$Mm@rtBNlROcN16VMdT6P{L2dn= z!cqt%oD&yM#7UfN3F1uG-wQ5t_n`&tW6h~;#=(ExkM;ep762~=?HjP%s;<8WV?j&` z+ADx}8;6&~;l1q#Zz|v!*WXV Qv5QX}GM43fkeSyx`7~oyVttiD8$_gpL zr+b)`c;ixz$15e?Z{a)jg>uZ5N6&Z~aq?RP&~wwf_`tHt?uY54mGveOE*s-z;9r9V zOcf~uCyKU(86` b@rJ_t)@RUGI z%()`!i8;Rs&p^!iU6dI~v~vKT5Wsp=w^K8tPSgcy6-3T_%f&ArCkp-q`8eH_e4Da| z#m_5Pz727H34TL7P3P}KJRLzbV9; HA8s)=I?5F{Vp`!?tYKm!HNV4+Ew6VwX(09QYd2V zP6xT`TZ+fEcp*Je=lv{ECf>g(_)WZ@E9!~&Tj6;~8Ho2IMVaxQme_sgi3y&do~p2T zFX6vglMi_JMgknV_x6oO@l1BB5%(TIVe@=+(yko$_C+oZvnDl*WqWU(=zLKoqHTg8 zBKk*BPeeD8i2hW#6GqW$BKkprj@J3?%0=(dPyNshlh6&6(0K(YLN`=G_o1jK9(9({ zu?>LT kfF<dcgv(*f_<@+Art~0F>yY>MPZ%fkb?SZ+ zk^~9KR}zxG{z&eZk>n7PoumkuBA}<0> ~v| z8bPu1ZZInLEAuFFMicaAekAh~SXbSVE9Ma7X3Fgur26d{q NT=I6PC%y-Vc)WJ%jaU83KLJV4Q?tgoNN03Bg!@1PL;NLh7?8d AS425u=6GmC=ZG_$A$Q?+KS zAkZxJhZcuLgEg!HPyNC4&*JVA;c)i~@$NknV6o|gvNe;WV5g-i$nHK}%`iM@w2MTT z)|~b+;hv@m-H&VgR!R4cUmzIia$kK4-z gNg87hvVbhP#0mkNeR?GichX;vpp8o^iqV;>UIi~wK zrWlTC5@C}1-3gf7UD^75FP;E&nIwgyM&{6B0nNu(M1$r#b>qntWip=Sf^#Gcx2T8l zNZr>aAR-iBh*nVUoehj^Sdu~9WEnsogUJBbL_$+vlnKol-Cs&*64g8-u~*WLqZ#jq zCRRsd1)6+Xtb&_?o)wpnIrCIC=o=6{ZLSp}klyBC{Rklhr14@MtRNjT9UPK?s7`sC zVm*(hIxlcO&oT$ti%ab(4scC zMkiN!<-<~~-Dd(?0U&3zDW&@YBr(Z#{qtjI1xWTYkW!>U|Z z-H@>UhnAUS5-%I_lf?YUj##u1*zEAumCuhZb<-vAw WMku3k@RX 1mQ(WYf9xq9_x08c5W>BI>pL_qoCcGq%ti3G#XejUcas*Bj;a$t;R!(FAnQ zXBm8y9dvYU6?O}x?4U8n7?br`*TFcQ>;h3HvgZiKl2Csa^%~h*yYq(_*|{J|kMw`` zG6Z_0|BQs-2?;^0gy3m^1dquG*x2d`e-4enm;CFDe0ljP;tP#P6-4^dcp}C7>|Gpq zDTjZ&|A@d0Ptod0l+}2@fIPKoS?5U#p76iBFpAm}MUtlo07X~dNeDqWEy8(kUyAq3 zXYz-Dmlb64Kc;GuA%2Vw qYJa3c*NdM5u1N6CtWhoSVJo6M;d^Mr{U{EAutPnIC!>r{aEeE $ymp-2W*puSKX_4qCw3ANS;L@r&GkpjEA~9VdL2$+BsDzrgu0VJ+!5(N z@0df1K?@&qjL764_%rh@`cA^lKtSqCE8k5;nFKUJ5>QJ~Zxm4U0{~n!-WXdWm7nr= zr?lfCHErT+NkD7v*HNt!WkS_RLiM$%_e0f#@hUtYzd{FU1sio>_v6G=lA2%KydhDG zn^`vhI7*NcVKa-fk rz3PpkP#!Nl6B`>I20kCnp{lZ@5DOpb7coDD9fi6C&~sB58x}jI0WDl} zULcQ{4f!{~K31d35HF7g9qUL14e|288F18;v&}X%zQON0`ikP^&ti4fEEi>B&FvCv zR*HIJO>bei#F_+AW~`x;#u!6BM_Xvc-!+X|@%{Ab#mizpK|0^d_Y+9yw4Xrd02bpD z0rP2`dz1DRD7R&}eu|f`eeo5e&orC#v_ForSlR9LEUTZz7vA6G{qS{Q{b)!1kjBEl zr_op_9pbfbRHNpG(0Mw ~{*5lV;v2>WTe# zi!KoRX&|C!8T(fNs&5i;1Ocr20PmnW$}i(Rv*K4ss80=}gqosfKd|_@7x5SIB2GoK z6TAV7n48iA*{AN)iJmUXM06nBED=OR&lL4U^fQ7`BKk&|XtocsmfVTDlrJUu)A3yr zHLdKEP}w2fyw-j}eDS?HEnZP3TJ91&AX@$s^+d~QK_k)ff+#ZvvY@&x(W1_IFemE& zvR~KdNa&uF&=rdA61v$Ex^xL$3mF}y()j3zfx5O2m(U*#Do*}?J{zj1h4{1hHxc4b zX5Y%M; &piY4nqdIL% zp)eLrK(oG(AN* _D&)&)m+bCXpX5h$25XtdjFEK-;;pJbUlB8 zIkejWO f9Koe%){i(s zAk8;IfIwT%?}g Ck_9n6sq;H+`tSE!!nf>9^>Jw_ZR7W=K|+wC;_Fk8=W!+v2yRdv_%`TLU7 zPoy9Woe(X;P+Dlq+-%LD@zJjQ*iG4JW2d)ht(P|Q-L_CqYJl=&C>^F6jtY#B&c?Lu z&{E33x5qx|ux3!=l*6se!`ccO%s7sB!Q!ZunqiIni#@M!P0vC@ZEkm 7{i0=sqPt#8cPp&g~MSFWD>oe@D%_K;ha5h z6wcal>OxB##dVms4R;49&xYVghZlU?~b3PojQTl}>vA4KEBIv~p$)*Y{0?;Z!x z;00J_S5h6e^<`GP?vei3zN5d&dIDotN?V~>SttAnux7Rc>9lwrK{4G$x=%;(d6(6r z*`YP#8~muTY +67|HgW5Vtj2Ute8C;Vfy1qV)_GjibT zu^4L8H#Qb$++$;7r$;1NTiA&kICG#1dU`}`?2MC+w2e&g?lktH1eb+BW9wq^(QpT} zi&RgK@+^s*0TMZJjm6=XFW58T4LDhY{m5zovV1gyu%-QpzgJ=o*l>-i#ZtqXL5l|E zSlFT^i^88)@-Wa21pe{2>+BmT%EZ3*f)--mI8jgR+bisx*!Q6*Gxn_oIw;Ttv<3UV z{M*RBCz3GPD)N1UH0qcG>^vc@8GpVEK_%S}kRnn;hT4YZ^hX{9-W$Dn`Vfb*Nf?+l z<1GTO{Nk=38#be^BR#8eQG~U{98{tftu+3p;`ft|AkO}mIQv7(!ppjJLGVWE7L(;$ zPETQ1Kn!Qpt-qyi?G*K_TeN6}ZY^xXy7lW>W4D%Ik8&+dy^00^$a(>U0KO*yoGk(L zm+So;aJnDh9t4 %jGHC}n zX PD8^a+VXk zCG9W>DNCb;?SL838ch92HthIq3>=)i%uehSXFnSQHVckH+sA?i>f 3j!UXo+_bE3=o4nO_f=6z|YqMG8jot!JXLI zp}h1wzFR}_za!KdniydX9T;s58-$~@58)`S6Gv%DgzVg{UmJynI&0WVG&ozsUfaar zIO42f3dN`H#6;yKFZ+odbZ57XXP1TV>Oc`U;Nd6G0o}kN!}R*dDk=;ATKqPSe#0f= z$P4_3o!yPNg^Pnk&_tAp54VZ_5+6=sup^-oA088B5`o5tAW|z&_^o&zW;FSvas0Sz zD4aC0%=h($vNC=I ?{sEBqQ!CT`aeoFs04FY1Zg*Gt^KBz!Ydcx*&keBe1)9dt?>SN4k`p2S1r zq&BQ$V~E7^B7j3Idy9->d1FX`Wj$MeX0!f^4maD+<8EN%6{&Q8QWpla-B7~qY1bEM zk^S9@Rd$*LOcmXqq<|*oV-5GCT0x6A&-^o-hK{eI^UUKTbf%6JWn$_Y(SKs 19!au`EXheA3EI9CZ6V2%UZW&G9aNztrTh-{eu1 rC~yUj^ f7)h26OgB8mp%8NDrA>QXVGhA`SCLC zMxB>4MVWYcvqbM4QSZmge9)qwMSpq&gRGxLzfFQ1AwfPYdQY6V)f_VIXn_;=;aAfC zKa8B{+K)IvE26%N&Z3jY@f$x o>*W1a@VT=@*McW>rAfj4aT4J$Ke z(=G;Owr`8jESjG-2!FW=qw(MoqqBLZFF6~`Sm=zIcB14jeBo=k>-wTd>vgy$)_n)U zDN-bwi8z#v>$>Z|C9A(pV!!39=SW`6#)7#%?pQOw$M5Pnl46ys?8v4@aT5Z8{NKim z;&w!_G#5MBht`?V1g{kUZy}ty=o+6|bN@Awdszzy^EwA}?66Td9{`NlccOg(fVtP4 znpzx89}eanKQO}>7`E>;hl44&L08nDM41%zte`Ru<-MXF`=rd{a*DM*9xF##h*nUm zwtgs2Jgn{U)a5A0NN9$NGNI|BI(7pZZI6dS2k`kxjwbJrkwKY21MgNAp3%08x~KFO zMg#e%%mGM&WWy8e$Qo49#+u87=1D*q%uzl}C?(O>2TFNsja_wB0i;_1$)^CLbeLq> zdhY1-&8KK{GTLx5W;bFC-dYGUz)5vW()|$DWS61|9VXfSwPEw=PIG|mIlwzOz?psk zI|88CeCo*>27Lu>GEv!R4(88rM%Ud0Mq dSwnkcMPL9{1cqTnAkM`v)}jlx2F}8Lz*svFmvRML zLle-lKUxk&%Q=hb`LKD^8Ee>rwd`Z@CI+-SmIvmrX+T1vGS7>;_{=}12RoH}B5@*> zEc%P6E~vxtAE(lVsxVc42UT1}afHVAm3~`Fx$y~n^MAhw4fKSkd_Ld;umqjfZy5Nd zzOEEsiZUt1r=m=1w@#EP$iLi_w~s=(h7bXZp?d?LcHb|po0hwbtAhJ}Uj%dn=kF0< zPrH@)z dq0D2%zgM!clL%v_%x7BIT)$;u=q9w&COMyU} z3B>`8>hNMknHWDvFq<^5i>N2Y`-G<_#?KRF#`uo_Rn7OSrAo?}??;XhJ!`>xmb~96 zo7;P-^CJIzzc3`jFmq84eY>qX(HWvlM2`{#5z)_!dLsHBiRj(J&oZJvC8Dd9?{~DR zU%p>iJ)M?PQ6^d*6ig#pu84Y~Wx2pl4D2D(LZPOwEjdeV@^s3Zgv<&<*B40W6bW64 z=q{mqQ9>6hp;Kcp)^&1@{`r1&(757!zt>-b>S_6Y^Z7S1(|U$|t03PmU b1nVMk0)3J;>wgv=fbB>JbG-b^6so(+o6W6E5t5z(x;w8)a56 z-*3?ujQ@JRU#LWOZHes91<#59R)4aGApjyHV`S558$I7I=qrXm&-eSRj?S0mqD*{w zOhT|y)N6dXB0>^|U?=CxS~LP*LiQT@lG}s$LSe$y^ZhD*A4eV0BRcPQ1BG|U?4$Q_ z%m5)|^o)>yI3ag(LT3MB^knygkgK2K!Hb5}iPp_6DW)lZis!-A@POv;<9JV%1gB3W zES#*&6Tawr#-U?-K$sLzF6#~s(fmexAIE(vzl~sM0}^0*b1?4^7z$v-;gG=q^DSFH z7VqOYVGc!X4%FsQ2Khk=Cn(jr4{VP)nwxl^mtDvDeEMf&pI3IP;1thK=FnOIntF=o za&28>&xtZ=Y!mS)nZzYgk2MJCMl5Y=SC11H#EI)i#7UeC197JJfo=L+ax8R;XMj1i zw{q|wfCyN`x!nLSO>DlfV`8%mO%VB>`^a#Lrvi8}9A1A8?*l)0BLUBNAJ}h@Et}UE zdLPZ4y(iqt!3-lXk{UJzm~!`l?K6i&6D{pNu>UZ3J6%7;GbBV8P(M*70lhDPk$?t^ zdZU1%;{e#?KCpx0MJTlSu5F
z@dCbz?#rcJfb#MBXErTf?`yzVQM_JTbd~1GHAI ZUU+}d^4Hhwk<)Ewv z=1Htx1m+asg4E$bjMUXTKq{%8aQ72oDl9O6o0?&X AtX>smV)Yu(K~9BO{g$XFR@W4LAy$7Nf(FKFws;-^ zTJXad@i6R=E|XI8@@cyOU3?ngAdR248>OLiQ6uy#)Dz_cnrAC3DNm2~FZl7C Zu2Rh!LFi;0Ivx4FzsBwTRy5cV>hix=9kc zBne%iaB;-2i4wY90tKO)A*0JCbj7fKwoLH@a&_Zq2jv!|J$XMxV9pZ$9!Tq^oSo6q zJ>G$1_E4X@0imc3zZnd_pr$3&P<9x~;V4fJw!3YPqF`}*7-cvR*qZKCq*En?;6gqm zg?^8_7Eq}Np0C!>nYcidiHZ9~PpDUmL_IO_k{Akc5s?VTc2IXBs5_Wo;EJ$Px+N@V z7~`If=S~Svl!WI+3C|r8o`vP`(0LOdO)u1wL={gddU%M7>Ma4W{F|6k+>URR+y=Hn z*ot9NF*QJE(gaZ^CKU^UiAhsLJuzvo7@Bk@6%yT^@TbrS7O-Qh(E^@(fP!Z<0oC)h z+rY9@t2e2zec;FQZ!J36t3{c}9wL$bji@KGBg&C|6eQ`BidD-Q0)0~Ph=ibzgdki( zFi1l10|GJfEjnL{IA2Dg5%{uZi;*wM_Y+@egsyN>LF+60{ULJ*l=}XVyZ~)0C)IE- z*hoPF%`j$ATqtH3yD3^yRazYT__54RRJA0kB$vFeqj1TzAg;YUgw9iPTG{M_W=r|C zC~-@D9R|HmO`cEGg~^DdxxT9I7z4R>WK^E;?wpL*H-ii*kb4+ph*x67aqs&l8k)p| zHvkNKhsXw16h<`7Ihuo4Sa0tpG*XmYgr#jZQ(*5984Na=plHQGyvae7Y%+paP9SK? z!ZOupzI<$kNa>pcHmNTVk?s=Mhid98-|E