diff --git a/crates/alien-bindings-node/src/lib.rs b/crates/alien-bindings-node/src/lib.rs index a3333e145..747ed68f4 100644 --- a/crates/alien-bindings-node/src/lib.rs +++ b/crates/alien-bindings-node/src/lib.rs @@ -1,5 +1,5 @@ //! `alien-bindings-node` — a napi-rs addon exposing `alien-bindings` -//! storage / kv / queue / vault / container to JavaScript. +//! storage / kv / queue / vault / container / postgres to JavaScript. //! //! This crate is a pure argument/error translation layer. It contains no //! provider logic: every method marshals arguments across the JS boundary, @@ -11,6 +11,7 @@ mod container; mod error; mod kv; +mod postgres; mod queue; mod storage; mod vault; @@ -22,6 +23,7 @@ use std::sync::Arc; pub use container::ContainerHandle; pub use kv::KvHandle; +pub use postgres::{PostgresConnectionJs, PostgresHandle}; pub use queue::QueueHandle; pub use storage::StorageHandle; pub use vault::VaultHandle; @@ -91,4 +93,15 @@ impl BindingsHandle { let container = inner.container(&name).await.map_err(map_alien_error)?; Ok(ContainerHandle::new(container)) } + + /// Resolve the Postgres binding named `name`. + /// + /// A cloud backend's password is read from its secret store here, so this is where a + /// secret-resolution failure surfaces — not on a later method call. + #[napi] + pub async fn postgres(&self, name: String) -> napi::Result { + let inner = self.inner.clone(); + let postgres = inner.postgres(&name).await.map_err(map_alien_error)?; + Ok(PostgresHandle::new(postgres)) + } } diff --git a/crates/alien-bindings-node/src/postgres.rs b/crates/alien-bindings-node/src/postgres.rs new file mode 100644 index 000000000..8aa97be6d --- /dev/null +++ b/crates/alien-bindings-node/src/postgres.rs @@ -0,0 +1,137 @@ +//! Postgres binding handle. Thin argument/error translation over the `Postgres` +//! trait. +//! +//! Postgres is connection-only: there are no operations to forward, just the connection +//! details the Rust provider already resolved (including reading a cloud backend's +//! password from its secret store). The handle is therefore synchronous — every field is +//! in memory by the time `BindingsHandle::postgres` returns. + +use alien_bindings::{Postgres, PostgresConnectionParams}; +use napi_derive::napi; +use std::sync::Arc; + +/// Everything a Postgres driver needs to connect. +#[napi(object)] +pub struct PostgresConnectionJs { + /// `postgres://user:password@host:port/database?sslmode=`, with the username, + /// password, and database percent-encoded. + pub connection_string: String, + /// Address to dial (the cluster writer endpoint for Aurora). + pub host: String, + /// TCP port. + pub port: u32, + /// Database name. + pub database: String, + /// Role to connect as. + pub username: String, + /// Connection password, already resolved from the cloud secret store where applicable. + pub password: String, + /// `disable` (local or explicit BYO plaintext), `verify-ca` (Cloud SQL), or + /// `verify-full` (BYO, Aurora, and Flexible Server). + pub sslmode: String, + /// PEM-encoded root CAs used by the verified TLS modes. + pub ca_certificates: Vec, +} + +/// Handle to a resolved Postgres binding. +#[napi] +pub struct PostgresHandle { + inner: Arc, +} + +impl PostgresHandle { + pub(crate) fn new(inner: Arc) -> Self { + Self { inner } + } +} + +/// Translate resolved connection parameters into their JS shape. +fn connection_to_js(params: &PostgresConnectionParams) -> PostgresConnectionJs { + PostgresConnectionJs { + connection_string: params.connection_string(), + host: params.host.clone(), + port: u32::from(params.port), + database: params.database.clone(), + username: params.username.clone(), + password: params.password.clone(), + sslmode: params.sslmode().as_str().to_string(), + ca_certificates: params.ca_certificates().to_vec(), + } +} + +#[napi] +impl PostgresHandle { + /// Return the resolved connection details. + #[napi] + pub fn connection(&self) -> PostgresConnectionJs { + connection_to_js(self.inner.connection_params()) + } +} + +#[cfg(test)] +mod tests { + use super::*; + use alien_bindings::PostgresTlsPolicy; + + /// The JS shape must carry every field verbatim, with the connection string derived + /// from the same params (not stored separately) and `sslmode` as its wire string. + #[test] + fn connection_to_js_maps_every_field() { + let params = PostgresConnectionParams::new( + "cluster.rds.amazonaws.com".to_string(), + 5432, + "app".to_string(), + "alien".to_string(), + "p@ss/word".to_string(), + PostgresTlsPolicy::verify_full(vec![ + "-----BEGIN CERTIFICATE-----\nroot\n-----END CERTIFICATE-----".to_string(), + ]) + .unwrap(), + ); + + let js = connection_to_js(¶ms); + + assert_eq!(js.host, "cluster.rds.amazonaws.com"); + assert_eq!(js.port, 5432); + assert_eq!(js.database, "app"); + assert_eq!(js.username, "alien"); + assert_eq!(js.password, "p@ss/word"); + assert_eq!(js.sslmode, "verify-full"); + assert_eq!(js.ca_certificates, params.ca_certificates()); + assert_eq!( + js.connection_string, + "postgres://alien:p%40ss%2Fword@cluster.rds.amazonaws.com:5432/app?sslmode=verify-full" + ); + } + + /// Each `SslMode` must reach JS as the exact string the `sslmode` query parameter + /// uses, so the TS layer can map it to a driver TLS setting without guessing. + #[test] + fn connection_to_js_reports_each_sslmode_as_its_wire_string() { + for (tls, expected) in [ + (PostgresTlsPolicy::disabled(), "disable"), + ( + PostgresTlsPolicy::verify_ca(vec![ + "-----BEGIN CERTIFICATE-----\nroot\n-----END CERTIFICATE-----".to_string(), + ]) + .unwrap(), + "verify-ca", + ), + ( + PostgresTlsPolicy::verify_full_with_system_roots(), + "verify-full", + ), + ] { + let params = PostgresConnectionParams::new( + "h".to_string(), + 5432, + "db".to_string(), + "u".to_string(), + "p".to_string(), + tls, + ); + + assert_eq!(connection_to_js(¶ms).sslmode, expected); + } + } +} diff --git a/crates/alien-bindings/Cargo.toml b/crates/alien-bindings/Cargo.toml index e7c22de2d..bf62d8eae 100644 --- a/crates/alien-bindings/Cargo.toml +++ b/crates/alien-bindings/Cargo.toml @@ -60,7 +60,11 @@ base64 = { workspace = true, optional = true } k8s-openapi = { version = "0.25.0", features = ["v1_33"], optional = true } [dev-dependencies] +# `test-utils` on each cloud client crate enables its mockall `Mock*Api` doubles, which +# the cloud Postgres providers' unit tests use to fake the secret store. alien-aws-clients = { workspace = true, features = ["test-utils"] } +alien-gcp-clients = { workspace = true, features = ["test-utils"] } +alien-azure-clients = { workspace = true, features = ["test-utils"] } axum = { workspace = true, features = ["tokio", "http1", "json"] } dotenvy = { workspace = true } tokio = { workspace = true, features = ["macros", "rt-multi-thread"] } diff --git a/crates/alien-bindings/src/bindings.rs b/crates/alien-bindings/src/bindings.rs index df2e7ea78..6275b9b5d 100644 --- a/crates/alien-bindings/src/bindings.rs +++ b/crates/alien-bindings/src/bindings.rs @@ -1,14 +1,16 @@ //! App-facing convenience API for accessing bindings. //! //! [`Bindings`] wraps a [`crate::provider::LazyEnvBindingsProvider`], giving application -//! code a small, stable surface — `storage`, `kv`, `queue`, `vault`, `container` — instead of the full -//! [`crate::traits::BindingsProviderApi`] used internally by the manager and controllers. +//! code a small, stable surface — `storage`, `kv`, `queue`, `vault`, `container`, +//! `postgres` — instead of the full [`crate::traits::BindingsProviderApi`] used internally +//! by the manager and controllers. use crate::error::Result; use crate::provider::{BindingsProvider, LazyEnvBindingsProvider}; use crate::refreshing::{RefreshingKv, RefreshingQueue, RefreshingStorage, RefreshingVault}; use crate::traits::{ - BindingsProviderApi, Container, Kv, MessagePayload, Queue, QueueMessage, Storage, Vault, + BindingsProviderApi, Container, Kv, MessagePayload, Postgres, Queue, QueueMessage, Storage, + Vault, }; use std::collections::HashMap; use std::sync::Arc; @@ -164,6 +166,25 @@ impl Bindings { pub async fn container(&self, binding_name: &str) -> Result> { self.provider.load_container(binding_name).await } + + /// Loads the connection details for a linked Postgres database. + /// + /// Unlike the other kinds this returns no operations — Postgres has no gRPC service + /// and every backend speaks the same wire protocol, so the handle carries connection + /// details and the application connects with its own driver. + /// + /// The Local and External backends carry their password inline in the binding + /// environment variable, so their handle is resolved once and then cached. + /// + /// The three cloud backends carry only a locator for their password and read it from + /// the cloud secret store on **every** call — their handle is never cached. There is + /// no refreshing wrapper, so a handle keeps the password that was current when it was + /// created and calling this again is what picks up a rotated one. Each call is + /// therefore one secret-store read: hold the returned handle for the lifetime of a + /// connection pool rather than calling this per query. + pub async fn postgres(&self, binding_name: &str) -> Result> { + self.provider.load_postgres(binding_name).await + } } #[cfg(test)] diff --git a/crates/alien-bindings/src/error.rs b/crates/alien-bindings/src/error.rs index 6f03a59f9..202186e70 100644 --- a/crates/alien-bindings/src/error.rs +++ b/crates/alien-bindings/src/error.rs @@ -341,6 +341,49 @@ pub enum ErrorData { resource_id: Option, }, + /// Reading a cloud Postgres binding's password from its secret store failed. + /// + /// The retry and visibility metadata comes from the provider error being wrapped: + /// throttling remains retryable, while permanent provider failures do not become + /// retryable merely because they happened during secret resolution. + /// + /// `secret` is the locator, never the secret value. + #[error( + code = "POSTGRES_SECRET_RESOLUTION_FAILED", + message = "Failed to resolve the password for Postgres binding '{binding_name}' from secret '{secret}': {reason}", + retryable = "inherit", + internal = "inherit", + http_status_code = 502 + )] + PostgresSecretResolutionFailed { + /// Name of the Postgres binding whose password could not be resolved + binding_name: String, + /// The secret locator that was read (an ARN / name / URI — never the value) + secret: String, + /// What went wrong while reading the secret + reason: String, + }, + + /// A cloud Postgres secret was read, but did not contain a usable password. + /// + /// Provider responses with missing, empty, or malformed values cannot become valid + /// by retrying the same version. `secret` is the locator, never the secret value. + #[error( + code = "POSTGRES_SECRET_VALUE_INVALID", + message = "Secret '{secret}' for Postgres binding '{binding_name}' does not contain a valid password: {reason}", + retryable = "false", + internal = "false", + http_status_code = 502 + )] + PostgresSecretValueInvalid { + /// Name of the Postgres binding whose password value was invalid + binding_name: String, + /// The secret locator that was read (an ARN / name / URI — never the value) + secret: String, + /// Why the returned value cannot be used as a password + reason: String, + }, + /// Resource not found in the cloud platform. #[error( code = "RESOURCE_NOT_FOUND", diff --git a/crates/alien-bindings/src/lib.rs b/crates/alien-bindings/src/lib.rs index 761a884b4..a1c9547cc 100644 --- a/crates/alien-bindings/src/lib.rs +++ b/crates/alien-bindings/src/lib.rs @@ -8,9 +8,9 @@ pub use provider::BindingsProvider; pub use traits::{ ArtifactRegistry, ArtifactRegistryCredentials, ArtifactRegistryPermissions, AwsServiceAccountInfo, AzureServiceAccountInfo, Binding, BindingsProviderApi, Build, Container, - GcpServiceAccountInfo, ImpersonationRequest, Kv, Postgres, PostgresConnectionParams, Queue, - RegistryAuthMethod, RepositoryResponse, ServiceAccount, ServiceAccountInfo, SslMode, Storage, - Vault, Worker, + GcpServiceAccountInfo, ImpersonationRequest, InvalidPostgresCaCertificates, Kv, Postgres, + PostgresConnectionParams, PostgresTlsPolicy, Queue, RegistryAuthMethod, RepositoryResponse, + ServiceAccount, ServiceAccountInfo, SslMode, Storage, Vault, Worker, }; pub mod bindings; diff --git a/crates/alien-bindings/src/provider.rs b/crates/alien-bindings/src/provider.rs index 4a9c7a72e..55e0598f7 100644 --- a/crates/alien-bindings/src/provider.rs +++ b/crates/alien-bindings/src/provider.rs @@ -2,6 +2,7 @@ use crate::{ error::{binding_env_var, ErrorData, Result}, + providers::postgres::runtime::PostgresRuntime, traits::{ ArtifactRegistry, BindingsProviderApi, Build, Container, Kv, Postgres, Queue, ServiceAccount, Storage, Vault, Worker, @@ -20,10 +21,13 @@ use tokio::sync::{OnceCell, RwLock}; /// Direct platform-specific bindings provider. /// Routes to appropriate platform implementations based on binding configuration. /// -/// Caches all loaded bindings by name. Each `load_*` call creates cloud clients +/// Caches loaded bindings by name. Each `load_*` call creates cloud clients /// (HTTP connection pools, token caches) which are expensive to initialize. Since /// the binding configuration is immutable for the provider's lifetime, the same /// binding name always produces the same client — so we cache on first load. +/// +/// Postgres has different caching semantics because cloud handles contain a resolved +/// password. Its dedicated runtime owns that policy and its secret-store clients. #[derive(Debug, Clone)] pub struct BindingsProvider { client_config: ClientConfig, @@ -32,6 +36,7 @@ pub struct BindingsProvider { /// `"{trait_name}:{binding_name}"` to avoid collisions across types. /// Each value is a `Box>` erased via `Any`. cache: Arc>>>, + postgres: Arc, } /// Environment-backed provider that defers cloud client configuration until the @@ -110,10 +115,12 @@ impl BindingsProvider { client_config: ClientConfig, bindings: HashMap, ) -> Result { + let postgres = Arc::new(PostgresRuntime::new(client_config.clone())); Ok(Self { client_config, bindings, cache: Arc::new(RwLock::new(HashMap::new())), + postgres, }) } @@ -1406,22 +1413,8 @@ impl BindingsProviderApi for BindingsProvider { } async fn load_postgres(&self, binding_name: &str) -> Result> { - if let Some(cached) = self - .get_cached::>("postgres", binding_name) - .await - { - return Ok(cached); - } - let binding: PostgresBinding = self.parse_binding(binding_name, "Postgres")?; - - let result: Arc = Arc::new( - crate::providers::postgres::local::LocalPostgres::from_binding(binding_name, &binding)?, - ); - - self.put_cache("postgres", binding_name, result.clone()) - .await; - Ok(result) + self.postgres.load(binding_name, &binding).await } async fn load_queue(&self, binding_name: &str) -> Result> { diff --git a/crates/alien-bindings/src/providers/postgres/aurora.rs b/crates/alien-bindings/src/providers/postgres/aurora.rs new file mode 100644 index 000000000..dd418abf2 --- /dev/null +++ b/crates/alien-bindings/src/providers/postgres/aurora.rs @@ -0,0 +1,251 @@ +//! AWS Aurora Serverless v2 Postgres provider. +//! +//! The binding carries the Secrets Manager **ARN** of the connection password, not the +//! password. This provider reads it with the workload's own identity (granted by the +//! `postgres/data-access` permission set) and builds the connection parameters. +//! +//! The password lives in Secrets Manager — not Parameter Store — so this is a different +//! client from the `vault` binding's `aws_parameter_store` provider. + +use crate::error::{ErrorData, Result}; +use crate::providers::postgres::{ + aws_rds_tls_policy, cloud::resolve_secret_locator, resolve_params, PostgresConnectionInput, +}; +use crate::traits::PostgresConnectionParams; +use alien_aws_clients::secrets_manager::{GetSecretValueRequest, SecretsManagerApi}; +use alien_core::bindings::AuroraPostgresBinding; +use alien_error::{AlienError, Context}; +use std::sync::Arc; + +/// Reads the password from Secrets Manager and resolves the connection parameters. +/// +/// Aurora is dialed at the cluster **writer endpoint**, so `clusterEndpoint` — not a +/// `host` field — becomes the connection host. TLS verifies the official RDS root +/// set and the cluster endpoint hostname (`sslmode=verify-full`). +/// +/// Performs exactly one `GetSecretValue`; a failure is returned to the caller, which +/// owns any retry policy. +pub(crate) async fn resolve( + binding_name: &str, + binding: &AuroraPostgresBinding, + secrets: Arc, +) -> Result { + let secret_arn = resolve_secret_locator( + binding_name, + "passwordSecretArn", + &binding.password_secret_arn, + )?; + let password = read_password(binding_name, &secret_arn, secrets.as_ref()).await?; + + resolve_params( + binding_name, + PostgresConnectionInput { + host: &binding.cluster_endpoint, + port: &binding.port, + database: &binding.database, + username: &binding.username, + password: &password, + tls: aws_rds_tls_policy(), + }, + ) +} + +/// Reads the raw password the AWS controller stored as the secret's `SecretString`. +async fn read_password( + binding_name: &str, + secret_arn: &str, + secrets: &dyn SecretsManagerApi, +) -> Result { + let read_failed = |reason: &str| ErrorData::PostgresSecretResolutionFailed { + binding_name: binding_name.to_string(), + secret: secret_arn.to_string(), + reason: reason.to_string(), + }; + let invalid_value = |reason: &str| ErrorData::PostgresSecretValueInvalid { + binding_name: binding_name.to_string(), + secret: secret_arn.to_string(), + reason: reason.to_string(), + }; + + let response = secrets + .get_secret_value( + GetSecretValueRequest::builder() + .secret_id(secret_arn.to_string()) + .build(), + ) + .await + .context(read_failed("Secrets Manager GetSecretValue failed"))?; + + // Retrying the same secret version cannot turn a missing value into a password. + response + .secret_string + .filter(|password| !password.is_empty()) + .ok_or_else(|| AlienError::new(invalid_value("SecretString is missing or empty"))) +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::traits::SslMode; + use alien_aws_clients::secrets_manager::{GetSecretValueResponse, MockSecretsManagerApi}; + use alien_core::bindings::BindingValue; + + const SECRET_ARN: &str = "arn:aws:secretsmanager:us-east-1:000000000000:secret:pg-AbCdEf"; + + fn binding() -> AuroraPostgresBinding { + AuroraPostgresBinding { + cluster_endpoint: "cluster.cluster-abc.us-east-1.rds.amazonaws.com".into(), + port: BindingValue::value(5432), + database: "app".into(), + username: "alien".into(), + password_secret_arn: SECRET_ARN.into(), + } + } + + fn response(secret_string: Option<&str>) -> GetSecretValueResponse { + GetSecretValueResponse { + arn: Some(SECRET_ARN.to_string()), + name: Some("pg".to_string()), + version_id: None, + secret_binary: None, + secret_string: secret_string.map(str::to_string), + version_stages: None, + created_date: None, + } + } + + /// The happy path: the ARN is read verbatim, the *cluster endpoint* becomes the host, + /// TLS is required, and the password is percent-encoded into the URL. The password + /// deliberately contains every RFC 3986 sub-delim that `encodeURIComponent` leaves + /// literal, extending the encoding contract pinned in `local.rs` to this backend. + #[tokio::test] + async fn resolves_cluster_endpoint_with_verified_tls() { + let mut secrets = MockSecretsManagerApi::new(); + secrets + .expect_get_secret_value() + .times(1) + .withf(|request| { + request.secret_id == SECRET_ARN + && request.version_id.is_none() + && request.version_stage.is_none() + }) + .returning(|_| Ok(response(Some("a!b*c'd(e)f@/")))); + + let params = resolve("db", &binding(), Arc::new(secrets)) + .await + .expect("aurora binding resolves"); + + assert_eq!( + params.host, "cluster.cluster-abc.us-east-1.rds.amazonaws.com", + "Aurora dials the cluster writer endpoint, not a host field" + ); + assert_eq!(params.port, 5432); + assert_eq!(params.database, "app"); + assert_eq!(params.username, "alien"); + assert_eq!(params.password, "a!b*c'd(e)f@/"); + assert_eq!(params.sslmode(), SslMode::VerifyFull); + assert_eq!(params.ca_certificates().len(), 1); + assert_eq!( + params.ca_certificates()[0] + .matches("-----BEGIN CERTIFICATE-----") + .count(), + 108, + "the embedded official global set must contain every validated regional root" + ); + assert_eq!( + params.connection_string(), + "postgres://alien:a%21b%2Ac%27d%28e%29f%40%2F@\ + cluster.cluster-abc.us-east-1.rds.amazonaws.com:5432/app?sslmode=verify-full" + ); + } + + /// A failed secret read is upstream/transient — it must stay retryable so an + /// automated retry layer can recover, and it must never be reported as bad user config. + #[tokio::test] + async fn failed_secret_read_is_retryable() { + let mut secrets = MockSecretsManagerApi::new(); + secrets.expect_get_secret_value().times(1).returning(|_| { + Err(AlienError::new( + alien_client_core::ErrorData::RateLimitExceeded { + message: "GetSecretValue throttled".to_string(), + }, + )) + }); + + let error = resolve("db", &binding(), Arc::new(secrets)) + .await + .expect_err("a failed secret read must not resolve a connection"); + + assert_eq!(error.code, "POSTGRES_SECRET_RESOLUTION_FAILED"); + assert!(error.retryable, "an upstream read failure is retryable"); + assert!( + error.to_string().contains(SECRET_ARN), + "the error must name the secret locator so operators can find it, got: {error}" + ); + } + + /// Permanent and internal provider metadata must survive the domain wrapper. + #[tokio::test] + async fn permanent_secret_read_preserves_source_metadata() { + let mut secrets = MockSecretsManagerApi::new(); + secrets.expect_get_secret_value().times(1).returning(|_| { + Err(AlienError::new( + alien_client_core::ErrorData::SerializationError { + message: "Secrets Manager returned malformed JSON".to_string(), + }, + )) + }); + + let error = resolve("db", &binding(), Arc::new(secrets)) + .await + .expect_err("a malformed provider response must not resolve a connection"); + + assert_eq!(error.code, "POSTGRES_SECRET_RESOLUTION_FAILED"); + assert!(!error.retryable, "the provider error is permanent"); + assert!(error.internal, "the provider error is internal"); + } + + /// An empty stored secret must fail rather than silently connect with no password. + #[tokio::test] + async fn empty_secret_string_fails_resolution() { + for stored in [None, Some("")] { + let mut secrets = MockSecretsManagerApi::new(); + secrets + .expect_get_secret_value() + .times(1) + .returning(move |_| Ok(response(stored))); + + let error = resolve("db", &binding(), Arc::new(secrets)) + .await + .expect_err("an empty secret must not resolve a connection"); + + assert_eq!(error.code, "POSTGRES_SECRET_VALUE_INVALID"); + assert!(!error.retryable); + assert!(!error.internal); + } + } + + /// A malformed binding is user-fixable configuration: it must not be retryable, and + /// it must fail before any secret read is attempted. + #[tokio::test] + async fn unresolved_secret_arn_is_non_retryable_config_error() { + let mut secrets = MockSecretsManagerApi::new(); + secrets.expect_get_secret_value().never(); + + let mut malformed = binding(); + malformed.password_secret_arn = BindingValue::Expression(serde_json::json!({ + "Fn::GetAtt": ["PgSecret", "Id"] + })); + + let error = resolve("db", &malformed, Arc::new(secrets)) + .await + .expect_err("an unresolved secret ARN must not resolve a connection"); + + assert_eq!(error.code, "BINDING_CONFIG_INVALID"); + assert!(!error.retryable, "bad binding config is user-fixable"); + assert!( + error.to_string().contains("ALIEN_DB_BINDING"), + "the error must name the env var the user would edit, got: {error}" + ); + } +} diff --git a/crates/alien-bindings/src/providers/postgres/ca/README.md b/crates/alien-bindings/src/providers/postgres/ca/README.md new file mode 100644 index 000000000..a9c1edf20 --- /dev/null +++ b/crates/alien-bindings/src/providers/postgres/ca/README.md @@ -0,0 +1,37 @@ +# Embedded Postgres root CAs + +These files contain only self-signed root certificates. Server and intermediate +certificates are intentionally excluded because AWS and Azure rotate them routinely. + +- `aws-rds-global-roots.pem` is the official [Amazon RDS global bundle][aws-bundle]. + Despite its size, every entry is a region-specific, self-signed `CA:TRUE` root for + one of RDS's supported CA algorithms. It was refreshed on 2026-07-27. +- `azure-postgres-roots.pem` contains the roots [Microsoft currently recommends][azure-docs] + for Azure Database for PostgreSQL: DigiCert Global Root G2, Microsoft RSA Root + Certificate Authority 2017, and the DigiCert Global Root CA retained for China + regions and rotation extensions. The certificate sources are Microsoft's + [RSA 2017 root][microsoft-rsa-root], DigiCert's [Global Root G2][digicert-g2], + and DigiCert's [Global Root CA][digicert-g1]. It was refreshed on 2026-07-27. + +Shared-provider root rotation is handled by updating these files and releasing +`alien-bindings`. Cloud SQL is different: its CA is instance-specific and therefore +travels in the public binding. + +Before an update, inspect every certificate with OpenSSL and verify: + +1. `subject` equals `issuer`; +2. basic constraints contain `CA:TRUE`; +3. the provider documentation still lists the root. + +Current SHA-256 checksums: + +```text +e5bb2084ccf45087bda1c9bffdea0eb15ee67f0b91646106e466714f9de3c7e3 aws-rds-global-roots.pem +0d005d74af16cc8330cbaa19feb1308edede7b73978782e7a7a905541f5ca5fc azure-postgres-roots.pem +``` + +[aws-bundle]: https://truststore.pki.rds.amazonaws.com/global/global-bundle.pem +[azure-docs]: https://learn.microsoft.com/azure/postgresql/security/security-tls-how-to-connect +[microsoft-rsa-root]: https://www.microsoft.com/pkiops/certs/Microsoft%20RSA%20Root%20Certificate%20Authority%202017.crt +[digicert-g2]: https://cacerts.digicert.com/DigiCertGlobalRootG2.crt.pem +[digicert-g1]: https://cacerts.digicert.com/DigiCertGlobalRootCA.crt.pem diff --git a/crates/alien-bindings/src/providers/postgres/ca/aws-rds-global-roots.pem b/crates/alien-bindings/src/providers/postgres/ca/aws-rds-global-roots.pem new file mode 100644 index 000000000..99351b7e9 --- /dev/null +++ b/crates/alien-bindings/src/providers/postgres/ca/aws-rds-global-roots.pem @@ -0,0 +1,2736 @@ +-----BEGIN CERTIFICATE----- +MIID/jCCAuagAwIBAgIQdOCSuA9psBpQd8EI368/0DANBgkqhkiG9w0BAQsFADCB +lzELMAkGA1UEBhMCVVMxIjAgBgNVBAoMGUFtYXpvbiBXZWIgU2VydmljZXMsIElu +Yy4xEzARBgNVBAsMCkFtYXpvbiBSRFMxCzAJBgNVBAgMAldBMTAwLgYDVQQDDCdB +bWF6b24gUkRTIHNhLWVhc3QtMSBSb290IENBIFJTQTIwNDggRzExEDAOBgNVBAcM +B1NlYXR0bGUwIBcNMjEwNTE5MTgwNjI2WhgPMjA2MTA1MTkxOTA2MjZaMIGXMQsw +CQYDVQQGEwJVUzEiMCAGA1UECgwZQW1hem9uIFdlYiBTZXJ2aWNlcywgSW5jLjET +MBEGA1UECwwKQW1hem9uIFJEUzELMAkGA1UECAwCV0ExMDAuBgNVBAMMJ0FtYXpv +biBSRFMgc2EtZWFzdC0xIFJvb3QgQ0EgUlNBMjA0OCBHMTEQMA4GA1UEBwwHU2Vh +dHRsZTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAN6ftL6w8v3dB2yW +LjCxSP1D7ZsOTeLZOSCz1Zv0Gkd0XLhil5MdHOHBvwH/DrXqFU2oGzCRuAy+aZis +DardJU6ChyIQIciXCO37f0K23edhtpXuruTLLwUwzeEPdcnLPCX+sWEn9Y5FPnVm +pCd6J8edH2IfSGoa9LdErkpuESXdidLym/w0tWG/O2By4TabkNSmpdrCL00cqI+c +prA8Bx1jX8/9sY0gpAovtuFaRN+Ivg3PAnWuhqiSYyQ5nC2qDparOWuDiOhpY56E +EgmTvjwqMMjNtExfYx6Rv2Ndu50TriiNKEZBzEtkekwXInTupmYTvc7U83P/959V +UiQ+WSMCAwEAAaNCMEAwDwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQU4uYHdH0+ +bUeh81Eq2l5/RJbW+vswDgYDVR0PAQH/BAQDAgGGMA0GCSqGSIb3DQEBCwUAA4IB +AQBhxcExJ+w74bvDknrPZDRgTeMLYgbVJjx2ExH7/Ac5FZZWcpUpFwWMIJJxtewI +AnhryzM3tQYYd4CG9O+Iu0+h/VVfW7e4O3joWVkxNMb820kQSEwvZfA78aItGwOY +WSaFNVRyloVicZRNJSyb1UL9EiJ9ldhxm4LTT0ax+4ontI7zTx6n6h8Sr6r/UOvX +d9T5aUUENWeo6M9jGupHNn3BobtL7BZm2oS8wX8IVYj4tl0q5T89zDi2x0MxbsIV +5ZjwqBQ5JWKv7ASGPb+z286RjPA9R2knF4lJVZrYuNV90rHvI/ECyt/JrDqeljGL +BLl1W/UsvZo6ldLIpoMbbrb5 +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIIEBDCCAuygAwIBAgIQUfVbqapkLYpUqcLajpTJWzANBgkqhkiG9w0BAQsFADCB +mjELMAkGA1UEBhMCVVMxIjAgBgNVBAoMGUFtYXpvbiBXZWIgU2VydmljZXMsIElu +Yy4xEzARBgNVBAsMCkFtYXpvbiBSRFMxCzAJBgNVBAgMAldBMTMwMQYDVQQDDCpB +bWF6b24gUkRTIG1lLWNlbnRyYWwtMSBSb290IENBIFJTQTIwNDggRzExEDAOBgNV +BAcMB1NlYXR0bGUwIBcNMjIwNTA2MjMyMDA5WhgPMjA2MjA1MDcwMDIwMDlaMIGa +MQswCQYDVQQGEwJVUzEiMCAGA1UECgwZQW1hem9uIFdlYiBTZXJ2aWNlcywgSW5j +LjETMBEGA1UECwwKQW1hem9uIFJEUzELMAkGA1UECAwCV0ExMzAxBgNVBAMMKkFt +YXpvbiBSRFMgbWUtY2VudHJhbC0xIFJvb3QgQ0EgUlNBMjA0OCBHMTEQMA4GA1UE +BwwHU2VhdHRsZTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAJIeovu3 +ewI9FVitXMQzvkh34aQ6WyI4NO3YepfJaePiv3cnyFGYHN2S1cR3UQcLWgypP5va +j6bfroqwGbCbZZcb+6cyOB4ceKO9Ws1UkcaGHnNDcy5gXR7aCW2OGTUfinUuhd2d +5bOGgV7JsPbpw0bwJ156+MwfOK40OLCWVbzy8B1kITs4RUPNa/ZJnvIbiMu9rdj4 +8y7GSFJLnKCjlOFUkNI5LcaYvI1+ybuNgphT3nuu5ZirvTswGakGUT/Q0J3dxP0J +pDfg5Sj/2G4gXiaM0LppVOoU5yEwVewhQ250l0eQAqSrwPqAkdTg9ng360zqCFPE +JPPcgI1tdGUgneECAwEAAaNCMEAwDwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQU +/2AJVxWdZxc8eJgdpbwpW7b0f7IwDgYDVR0PAQH/BAQDAgGGMA0GCSqGSIb3DQEB +CwUAA4IBAQBYm63jTu2qYKJ94gKnqc+oUgqmb1mTXmgmp/lXDbxonjszJDOXFbri +3CCO7xB2sg9bd5YWY8sGKHaWmENj3FZpCmoefbUx++8D7Mny95Cz8R32rNcwsPTl +ebpd9A/Oaw5ug6M0x/cNr0qzF8Wk9Dx+nFEimp8RYQdKvLDfNFZHjPa1itnTiD8M +TorAqj+VwnUGHOYBsT/0NY12tnwXdD+ATWfpEHdOXV+kTMqFFwDyhfgRVNpTc+os +ygr8SwhnSCpJPB/EYl2S7r+tgAbJOkuwUvGT4pTqrzDQEhwE7swgepnHC87zhf6l +qN6mVpSnQKQLm6Ob5TeCEFgcyElsF5bH +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIICrjCCAjSgAwIBAgIRAOxu0I1QuMAhIeszB3fJIlkwCgYIKoZIzj0EAwMwgZYx +CzAJBgNVBAYTAlVTMSIwIAYDVQQKDBlBbWF6b24gV2ViIFNlcnZpY2VzLCBJbmMu +MRMwEQYDVQQLDApBbWF6b24gUkRTMQswCQYDVQQIDAJXQTEvMC0GA1UEAwwmQW1h +em9uIFJEUyB1cy13ZXN0LTIgUm9vdCBDQSBFQ0MzODQgRzExEDAOBgNVBAcMB1Nl +YXR0bGUwIBcNMjEwNTI0MjIwNjU5WhgPMjEyMTA1MjQyMzA2NTlaMIGWMQswCQYD +VQQGEwJVUzEiMCAGA1UECgwZQW1hem9uIFdlYiBTZXJ2aWNlcywgSW5jLjETMBEG +A1UECwwKQW1hem9uIFJEUzELMAkGA1UECAwCV0ExLzAtBgNVBAMMJkFtYXpvbiBS +RFMgdXMtd2VzdC0yIFJvb3QgQ0EgRUNDMzg0IEcxMRAwDgYDVQQHDAdTZWF0dGxl +MHYwEAYHKoZIzj0CAQYFK4EEACIDYgAEz4bylRcGqqDWdP7gQIIoTHdBK6FNtKH1 +4SkEIXRXkYDmRvL9Bci1MuGrwuvrka5TDj4b7e+csY0llEzHpKfq6nJPFljoYYP9 +uqHFkv77nOpJJ633KOr8IxmeHW5RXgrZo0IwQDAPBgNVHRMBAf8EBTADAQH/MB0G +A1UdDgQWBBQQikVz8wmjd9eDFRXzBIU8OseiGzAOBgNVHQ8BAf8EBAMCAYYwCgYI +KoZIzj0EAwMDaAAwZQIwf06Mcrpw1O0EBLBBrp84m37NYtOkE/0Z0O+C7D41wnXi +EQdn6PXUVgdD23Gj82SrAjEAklhKs+liO1PtN15yeZR1Io98nFve+lLptaLakZcH ++hfFuUtCqMbaI8CdvJlKnPqT +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIIGCTCCA/GgAwIBAgIRALyWMTyCebLZOGcZZQmkmfcwDQYJKoZIhvcNAQEMBQAw +gZwxCzAJBgNVBAYTAlVTMSIwIAYDVQQKDBlBbWF6b24gV2ViIFNlcnZpY2VzLCBJ +bmMuMRMwEQYDVQQLDApBbWF6b24gUkRTMQswCQYDVQQIDAJXQTE1MDMGA1UEAwws +QW1hem9uIFJEUyBhcC1ub3J0aGVhc3QtMyBSb290IENBIFJTQTQwOTYgRzExEDAO +BgNVBAcMB1NlYXR0bGUwIBcNMjEwNTI0MjAyODAzWhgPMjEyMTA1MjQyMTI4MDNa +MIGcMQswCQYDVQQGEwJVUzEiMCAGA1UECgwZQW1hem9uIFdlYiBTZXJ2aWNlcywg +SW5jLjETMBEGA1UECwwKQW1hem9uIFJEUzELMAkGA1UECAwCV0ExNTAzBgNVBAMM +LEFtYXpvbiBSRFMgYXAtbm9ydGhlYXN0LTMgUm9vdCBDQSBSU0E0MDk2IEcxMRAw +DgYDVQQHDAdTZWF0dGxlMIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEA +wGFiyDyCrGqgdn4fXG12cxKAAfVvhMea1mw5h9CVRoavkPqhzQpAitSOuMB9DeiP +wQyqcsiGl/cTEau4L+AUBG8b9v26RlY48exUYBXj8CieYntOT9iNw5WtdYJa3kF/ +JxgI+HDMzE9cmHDs5DOO3S0uwZVyra/xE1ymfSlpOeUIOTpHRJv97CBUEpaZMUW5 +Sr6GruuOwFVpO5FX3A/jQlcS+UN4GjSRgDUJuqg6RRQldEZGCVCCmodbByvI2fGm +reGpsPJD54KkmAX08nOR8e5hkGoHxq0m2DLD4SrOFmt65vG47qnuwplWJjtk9B3Z +9wDoopwZLBOtlkPIkUllWm1P8EuHC1IKOA+wSP6XdT7cy8S77wgyHzR0ynxv7q/l +vlZtH30wnNqFI0y9FeogD0TGMCHcnGqfBSicJXPy9T4fU6f0r1HwqKwPp2GArwe7 +dnqLTj2D7M9MyVtFjEs6gfGWXmu1y5uDrf+CszurE8Cycoma+OfjjuVQgWOCy7Nd +jJswPxAroTzVfpgoxXza4ShUY10woZu0/J+HmNmqK7lh4NS75q1tz75in8uTZDkV +be7GK+SEusTrRgcf3tlgPjSTWG3veNzFDF2Vn1GLJXmuZfhdlVQDBNXW4MNREExS +dG57kJjICpT+r8X+si+5j51gRzkSnMYs7VHulpxfcwECAwEAAaNCMEAwDwYDVR0T +AQH/BAUwAwEB/zAdBgNVHQ4EFgQU4JWOpDBmUBuWKvGPZelw87ezhL8wDgYDVR0P +AQH/BAQDAgGGMA0GCSqGSIb3DQEBDAUAA4ICAQBRNLMql7itvXSEFQRAnyOjivHz +l5IlWVQjAbOUr6ogZcwvK6YpxNAFW5zQr8F+fdkiypLz1kk5irx9TIpff0BWC9hQ +/odMPO8Gxn8+COlSvc+dLsF2Dax3Hvz0zLeKMo+cYisJOzpdR/eKd0/AmFdkvQoM +AOK9n0yYvVJU2IrSgeJBiiCarpKSeAktEVQ4rvyacQGr+QAPkkjRwm+5LHZKK43W +nNnggRli9N/27qYtc5bgr3AaQEhEXMI4RxPRXCLsod0ehMGWyRRK728a+6PMMJAJ +WHOU0x7LCEMPP/bvpLj3BdvSGqNor4ZtyXEbwREry1uzsgODeRRns5acPwTM6ff+ +CmxO2NZ0OktIUSYRmf6H/ZFlZrIhV8uWaIwEJDz71qvj7buhQ+RFDZ9CNL64C0X6 +mf0zJGEpddjANHaaVky+F4gYMtEy2K2Lcm4JGTdyIzUoIe+atzCnRp0QeIcuWtF+ +s8AjDYCVFNypcMmqbRmNpITSnOoCHSRuVkY3gutVoYyMLbp8Jm9SJnCIlEWTA6Rm +wADOMGZJVn5/XRTRuetVOB3KlQDjs9OO01XN5NzGSZO2KT9ngAUfh9Eqhf1iRWSP +nZlRbQ2NRCuY/oJ5N59mLGxnNJSE7giEKEBRhTQ/XEPIUYAUPD5fca0arKRJwbol +l9Se1Hsq0ZU5f+OZKQ== +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +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 +5zT5+Bs= +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +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 +Hing +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIID/zCCAuegAwIBAgIRAI+asxQA/MB1cGyyrC0MPpkwDQYJKoZIhvcNAQELBQAw +gZcxCzAJBgNVBAYTAlVTMSIwIAYDVQQKDBlBbWF6b24gV2ViIFNlcnZpY2VzLCBJ +bmMuMRMwEQYDVQQLDApBbWF6b24gUkRTMQswCQYDVQQIDAJXQTEwMC4GA1UEAwwn +QW1hem9uIFJEUyBjYS13ZXN0LTEgUm9vdCBDQSBSU0EyMDQ4IEcxMRAwDgYDVQQH +DAdTZWF0dGxlMCAXDTIzMDkxMzIwMjEzNFoYDzIwNjMwOTEzMjEyMTMzWjCBlzEL +MAkGA1UEBhMCVVMxIjAgBgNVBAoMGUFtYXpvbiBXZWIgU2VydmljZXMsIEluYy4x +EzARBgNVBAsMCkFtYXpvbiBSRFMxCzAJBgNVBAgMAldBMTAwLgYDVQQDDCdBbWF6 +b24gUkRTIGNhLXdlc3QtMSBSb290IENBIFJTQTIwNDggRzExEDAOBgNVBAcMB1Nl +YXR0bGUwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDMHvQITTZcfl2O +yfzRIAPKwzzlc8eXWdXef7VUsbezg3lm9RC+vArO4JuAzta/aLw1D94wPSRm9JXX +NkP3obO6Ql80/0doooU6BAPceD0xmEWC4aCFT/5KWsD6Sy2/Rjwq3NKBTwzxLwYK +GqVsBp8AdrzDTmdRETC+Dg2czEo32mTDAA1uMgqrz6xxeTYroj8NTSTp6jfE6C0n +YgzYmVQCEIjHqI49j7k3jfT3P2skCVKGJwQzoZnerFacKzXsDB18uIqU7NaMc2cX +kOd0gRqpyKOzAHU2m5/S4jw4UHdkoI3E7nkayuen8ZPKH2YqWtTXUrXGhSTT34nX +yiFgu+vTAgMBAAGjQjBAMA8GA1UdEwEB/wQFMAMBAf8wHQYDVR0OBBYEFHzz1NTd +TOm9zAv4d8l6XCFKSdJfMA4GA1UdDwEB/wQEAwIBhjANBgkqhkiG9w0BAQsFAAOC +AQEAodBvd0cvXQYhFBef2evnuI9XA+AC/Q9P1nYtbp5MPA4aFhy5v9rjW8wwJX14 +l+ltd2o3tz8PFDBZ1NX2ooiWVlZthQxKn1/xDVKsTXHbYUXItPQ3jI5IscB5IML8 +oCzAbkoLXsSPNOVFP5P4l4cZEMqHGRnBag7hLJZvmvzZSBnz+ioC2jpjVluF8kDX +fQGNjqPECik68CqbSV0SaQ0cgEoYTDjwON5ZLBeS8sxR2abE/gsj4VFYl5w/uEBd +w3Tt9uGfIy+wd2tNj6isGC6PcbPMjA31jd+ifs2yNzigqkcYTTWFtnvh4a8xiecm +GHu2EgH0Jqzz500N7L3uQdPkdg== +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIIGCTCCA/GgAwIBAgIRALnItUH64VieFPvDUCOG5E0wDQYJKoZIhvcNAQEMBQAw +gZwxCzAJBgNVBAYTAlVTMSIwIAYDVQQKDBlBbWF6b24gV2ViIFNlcnZpY2VzLCBJ +bmMuMRMwEQYDVQQLDApBbWF6b24gUkRTMQswCQYDVQQIDAJXQTE1MDMGA1UEAwws +QW1hem9uIFJEUyBhcC1zb3V0aGVhc3QtNSBSb290IENBIFJTQTQwOTYgRzExEDAO +BgNVBAcMB1NlYXR0bGUwIBcNMjQwNTE1MjE1MDQxWhgPMjEyNDA1MTUyMjUwNDFa +MIGcMQswCQYDVQQGEwJVUzEiMCAGA1UECgwZQW1hem9uIFdlYiBTZXJ2aWNlcywg +SW5jLjETMBEGA1UECwwKQW1hem9uIFJEUzELMAkGA1UECAwCV0ExNTAzBgNVBAMM +LEFtYXpvbiBSRFMgYXAtc291dGhlYXN0LTUgUm9vdCBDQSBSU0E0MDk2IEcxMRAw +DgYDVQQHDAdTZWF0dGxlMIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEA +u2Bc0RjN0vB7EM+h0yts1jSqzDd1v5FcxCDbC7vKPVCq/1pYNTIxQj77HiMcYtvL +Bfi9AQFibU1C9gN62kUmSe0QaNGqQL2g/6YpB4qI8psIsCt3aIigbhwEEpebhIU/ +vhr/pvLKhkQOSLxJVlX0j18hU5RVqOefCdFm9FmjFLge/m1Yzv2aFifRKIzdtkfp +4VZBzh7EzP6lxkU3SAcW9yRu/t4oY274ICnGisv2TR15hHlP0wUP6p5S3ot2q/xJ +57x8nzI3kQyC6a+n+kSzZzITboKWrsx3Jd2PdB4VC84P/YoAC3cwfmacmQVT01c8 +io1eO+BxCtWUNbwCv0Hd10bHI18rVzJhJPb3xg1i1Sc3sbcrADOONsuhxqwffjUe +XdVMdsjX2mYxQ520qnh5DwQkx3JyW6QwI/ueU9xbMuPTwAauXil7B9qx1IDViYUw +BvMDnxYbYHlDezYIc4WoNoA2KflMnNtN2WDiM7tvQKmWI8yYZrNdnqBD0HYR+neP +z69Tqy8i24CDoR9o3s5LxR58SgFPqu9RWu8uL6vfNLL2M8qQ4VueOWSyzRs9b3W5 +GVjA4U1CxlF3EirHEjciq6UEXr5+ZVf79iXGOwBVDzuim1LYfoTBgkMXKxyEzWYT +QCzf6VPW4x7eMQIriLl18YocHrqJQ7+BfMziYjOh0rcCAwEAAaNCMEAwDwYDVR0T +AQH/BAUwAwEB/zAdBgNVHQ4EFgQUQdyu9F6eLFuxe437iU/GXyFHU1owDgYDVR0P +AQH/BAQDAgGGMA0GCSqGSIb3DQEBDAUAA4ICAQCbbNUJRQ1gy1lKxCoszcyujCI5 +df0EGdadQL6BgaXws/uCFvHepB5lO62InAMTURCREeRtrCNyn1rEKnsqhAW0UokQ +Z+YOHcclgPsXmSQVjIUgnlE45mrPS/9mO8TzhCI3wyiELp6oa67RSiJ1Qcsypa4z +zHDkYdhFW3sxY8i2p2tqdkJz1ZEQd7FIpX+vrBVIkoqtGAn4urLaMq4CTNJCNepR +s4OGaoQVY43q2kcguRPDZVOFK5+GlrC2AzHMSVt5fFSCchgYxBZsS3UIVKm8YJ7v +1h85RwtNCHwwDt1uP43yLp5qfUmsfeaNmZiOk9AawxPCmy6XaSkQcLz+CQhG9T4W +siQMg6tagIUw1e4zFm7GXmeOCPc//ycGNDXgprMQzjK+AT4ed8iK+JnWlheMq5uf +XxQDSfakuAIEgJWPAzebjCo33O2j1PQfzbt1Ahs7f+gFczizfpatYkXcOTmLfG1l +QKj9jVNOIQSJt5PxH+QTDWQtkX/tGp/HS5a3dWusW/TnC3yakGqqfGx3cB/E00gF +geg0LYo1uOBjIYQbkp3Z6NKfcc/nb0ksV7feKm5f3rSO8NnA0Ou8YHb84LYDLYDf +VSR9SwSBhGw31otMTAsJdNTHJwfCcxfGtIvUfAsWUAh6qSo4es/hUV60pj01VWpq +Er+ItMFHuoSTmx18bw== +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIIECTCCAvGgAwIBAgIRANxgyBbnxgTEOpDul2ZnC0UwDQYJKoZIhvcNAQELBQAw +gZwxCzAJBgNVBAYTAlVTMSIwIAYDVQQKDBlBbWF6b24gV2ViIFNlcnZpY2VzLCBJ +bmMuMRMwEQYDVQQLDApBbWF6b24gUkRTMQswCQYDVQQIDAJXQTE1MDMGA1UEAwws +QW1hem9uIFJEUyBhcC1zb3V0aGVhc3QtMyBSb290IENBIFJTQTIwNDggRzExEDAO +BgNVBAcMB1NlYXR0bGUwIBcNMjEwNjEwMTgxOTA3WhgPMjA2MTA2MTAxOTE5MDda +MIGcMQswCQYDVQQGEwJVUzEiMCAGA1UECgwZQW1hem9uIFdlYiBTZXJ2aWNlcywg +SW5jLjETMBEGA1UECwwKQW1hem9uIFJEUzELMAkGA1UECAwCV0ExNTAzBgNVBAMM +LEFtYXpvbiBSRFMgYXAtc291dGhlYXN0LTMgUm9vdCBDQSBSU0EyMDQ4IEcxMRAw +DgYDVQQHDAdTZWF0dGxlMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA +xnwSDAChrMkfk5TA4Dk8hKzStDlSlONzmd3fTG0Wqr5+x3EmFT6Ksiu/WIwEl9J2 +K98UI7vYyuZfCxUKb1iMPeBdVGqk0zb92GpURd+Iz/+K1ps9ZLeGBkzR8mBmAi1S +OfpwKiTBzIv6E8twhEn4IUpHsdcuX/2Y78uESpJyM8O5CpkG0JaV9FNEbDkJeBUQ +Ao2qqNcH4R0Qcr5pyeqA9Zto1RswgL06BQMI9dTpfwSP5VvkvcNUaLl7Zv5WzLQE +JzORWePvdPzzvWEkY/3FPjxBypuYwssKaERW0fkPDmPtykktP9W/oJolKUFI6pXp +y+Y6p6/AVdnQD2zZjW5FhQIDAQABo0IwQDAPBgNVHRMBAf8EBTADAQH/MB0GA1Ud +DgQWBBT+jEKs96LC+/X4BZkUYUkzPfXdqTAOBgNVHQ8BAf8EBAMCAYYwDQYJKoZI +hvcNAQELBQADggEBAIGQqgqcQ6XSGkmNebzR6DhadTbfDmbYeN5N0Vuzv+Tdmufb +tMGjdjnYMg4B+IVnTKQb+Ox3pL9gbX6KglGK8HupobmIRtwKVth+gYYz3m0SL/Nk +haWPYzOm0x3tJm8jSdufJcEob4/ATce9JwseLl76pSWdl5A4lLjnhPPKudUDfH+1 +BLNUi3lxpp6GkC8aWUPtupnhZuXddolTLOuA3GwTZySI44NfaFRm+o83N1jp+EwD +6e94M4cTRzjUv6J3MZmSbdtQP/Tk1uz2K4bQZGP0PZC3bVpqiesdE/xr+wbu8uHr +cM1JXH0AmXf1yIkTgyWzmvt0k1/vgcw5ixAqvvE= +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIIEATCCAumgAwIBAgIRAMhw98EQU18mIji+unM2YH8wDQYJKoZIhvcNAQELBQAw +gZgxCzAJBgNVBAYTAlVTMSIwIAYDVQQKDBlBbWF6b24gV2ViIFNlcnZpY2VzLCBJ +bmMuMRMwEQYDVQQLDApBbWF6b24gUkRTMQswCQYDVQQIDAJXQTExMC8GA1UEAwwo +QW1hem9uIFJEUyBhcC1zb3V0aC0yIFJvb3QgQ0EgUlNBMjA0OCBHMTEQMA4GA1UE +BwwHU2VhdHRsZTAgFw0yMjA2MDYyMTQyMjJaGA8yMDYyMDYwNjIyNDIyMlowgZgx +CzAJBgNVBAYTAlVTMSIwIAYDVQQKDBlBbWF6b24gV2ViIFNlcnZpY2VzLCBJbmMu +MRMwEQYDVQQLDApBbWF6b24gUkRTMQswCQYDVQQIDAJXQTExMC8GA1UEAwwoQW1h +em9uIFJEUyBhcC1zb3V0aC0yIFJvb3QgQ0EgUlNBMjA0OCBHMTEQMA4GA1UEBwwH +U2VhdHRsZTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAIeeRoLfTm+7 +vqm7ZlFSx+1/CGYHyYrOOryM4/Z3dqYVHFMgWTR7V3ziO8RZ6yUanrRcWVX3PZbF +AfX0KFE8OgLsXEZIX8odSrq86+/Th5eZOchB2fDBsUB7GuN2rvFBbM8lTI9ivVOU +lbuTnYyb55nOXN7TpmH2bK+z5c1y9RVC5iQsNAl6IJNvSN8VCqXh31eK5MlKB4DT ++Y3OivCrSGsjM+UR59uZmwuFB1h+icE+U0p9Ct3Mjq3MzSX5tQb6ElTNGlfmyGpW +Kh7GQ5XU1KaKNZXoJ37H53woNSlq56bpVrKI4uv7ATpdpFubOnSLtpsKlpLdR3sy +Ws245200pC8CAwEAAaNCMEAwDwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQUp0ki +6+eWvsnBjQhMxwMW5pwn7DgwDgYDVR0PAQH/BAQDAgGGMA0GCSqGSIb3DQEBCwUA +A4IBAQB2V8lv0aqbYQpj/bmVv/83QfE4vOxKCJAHv7DQ35cJsTyBdF+8pBczzi3t +3VNL5IUgW6WkyuUOWnE0eqAFOUVj0yTS1jSAtfl3vOOzGJZmWBbqm9BKEdu1D8O6 +sB8bnomwiab2tNDHPmUslpdDqdabbkWwNWzLJ97oGFZ7KNODMEPXWKWNxg33iHfS +/nlmnrTVI3XgaNK9qLZiUrxu9Yz5gxi/1K+sG9/Dajd32ZxjRwDipOLiZbiXQrsd +qzIMY4GcWf3g1gHL5mCTfk7dG22h/rhPyGV0svaDnsb+hOt6sv1McMN6Y3Ou0mtM +/UaAXojREmJmTSCNvs2aBny3/2sy +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +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 +Pz5G0lqE904= +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIICrjCCAjSgAwIBAgIRAMnRxsKLYscJV8Qv5pWbL7swCgYIKoZIzj0EAwMwgZYx +CzAJBgNVBAYTAlVTMSIwIAYDVQQKDBlBbWF6b24gV2ViIFNlcnZpY2VzLCBJbmMu +MRMwEQYDVQQLDApBbWF6b24gUkRTMQswCQYDVQQIDAJXQTEvMC0GA1UEAwwmQW1h +em9uIFJEUyBzYS1lYXN0LTEgUm9vdCBDQSBFQ0MzODQgRzExEDAOBgNVBAcMB1Nl +YXR0bGUwIBcNMjEwNTE5MTgxNjAxWhgPMjEyMTA1MTkxOTE2MDFaMIGWMQswCQYD +VQQGEwJVUzEiMCAGA1UECgwZQW1hem9uIFdlYiBTZXJ2aWNlcywgSW5jLjETMBEG +A1UECwwKQW1hem9uIFJEUzELMAkGA1UECAwCV0ExLzAtBgNVBAMMJkFtYXpvbiBS +RFMgc2EtZWFzdC0xIFJvb3QgQ0EgRUNDMzg0IEcxMRAwDgYDVQQHDAdTZWF0dGxl +MHYwEAYHKoZIzj0CAQYFK4EEACIDYgAEjFOCZgTNVKxLKhUxffiDEvTLFhrmIqdO +dKqVdgDoELEzIHWDdC+19aDPitbCYtBVHl65ITu/9pn6mMUl5hhUNtfZuc6A+Iw1 +sBe0v0qI3y9Q9HdQYrGgeHDh8M5P7E2ho0IwQDAPBgNVHRMBAf8EBTADAQH/MB0G +A1UdDgQWBBS5L7/8M0TzoBZk39Ps7BkfTB4yJTAOBgNVHQ8BAf8EBAMCAYYwCgYI +KoZIzj0EAwMDaAAwZQIwI43O0NtWKTgnVv9z0LO5UMZYgSve7GvGTwqktZYCMObE +rUI4QerXM9D6JwLy09mqAjEAypfkdLyVWtaElVDUyHFkihAS1I1oUxaaDrynLNQK +Ou/Ay+ns+J+GyvyDUjBpVVW1 +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +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 +zDY= +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIIECTCCAvGgAwIBAgIRAK5EYG3iHserxMqgg+0EFjgwDQYJKoZIhvcNAQELBQAw +gZwxCzAJBgNVBAYTAlVTMSIwIAYDVQQKDBlBbWF6b24gV2ViIFNlcnZpY2VzLCBJ +bmMuMRMwEQYDVQQLDApBbWF6b24gUkRTMQswCQYDVQQIDAJXQTE1MDMGA1UEAwws +QW1hem9uIFJEUyBhcC1ub3J0aGVhc3QtMyBSb290IENBIFJTQTIwNDggRzExEDAO +BgNVBAcMB1NlYXR0bGUwIBcNMjEwNTI0MjAyMzE2WhgPMjA2MTA1MjQyMTIzMTZa +MIGcMQswCQYDVQQGEwJVUzEiMCAGA1UECgwZQW1hem9uIFdlYiBTZXJ2aWNlcywg +SW5jLjETMBEGA1UECwwKQW1hem9uIFJEUzELMAkGA1UECAwCV0ExNTAzBgNVBAMM +LEFtYXpvbiBSRFMgYXAtbm9ydGhlYXN0LTMgUm9vdCBDQSBSU0EyMDQ4IEcxMRAw +DgYDVQQHDAdTZWF0dGxlMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA +s1L6TtB84LGraLHVC+rGPhLBW2P0oN/91Rq3AnYwqDOuTom7agANwEjvLq7dSRG/ +sIfZsSV/ABTgArZ5sCmLjHFZAo8Kd45yA9byx20RcYtAG8IZl+q1Cri+s0XefzyO +U6mlfXZkVe6lzjlfXBkrlE/+5ifVbJK4dqOS1t9cWIpgKqv5fbE6Qbq4LVT+5/WM +Vd2BOljuBMGMzdZubqFKFq4mzTuIYfnBm7SmHlZfTdfBYPP1ScNuhpjuzw4n3NCR +EdU6dQv04Q6th4r7eiOCwbWI9LkmVbvBe3ylhH63lApC7MiiPYLlB13xBubVHVhV +q1NHoNTi+zA3MN9HWicRxQIDAQABo0IwQDAPBgNVHRMBAf8EBTADAQH/MB0GA1Ud +DgQWBBSuxoqm0/wjNiZLvqv+JlQwsDvTPDAOBgNVHQ8BAf8EBAMCAYYwDQYJKoZI +hvcNAQELBQADggEBAFfTK/j5kv90uIbM8VaFdVbr/6weKTwehafT0pAk1bfLVX+7 +uf8oHgYiyKTTl0DFQicXejghXTeyzwoEkWSR8c6XkhD5vYG3oESqmt/RGvvoxz11 +rHHy7yHYu7RIUc3VQG60c4qxXv/1mWySGwVwJrnuyNT9KZXPevu3jVaWOVHEILaK +HvzQ2YEcWBPmde/zEseO2QeeGF8FL45Q1d66wqIP4nNUd2pCjeTS5SpB0MMx7yi9 +ki1OH1pv8tOuIdimtZ7wkdB8+JSZoaJ81b8sRrydRwJyvB88rftuI3YB4WwGuONT +ZezUPsmaoK69B0RChB0ofDpAaviF9V3xOWvVZfo= +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +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 +1axqeKpI0F5SBUPsaVNYY2uNFg== +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIIECDCCAvCgAwIBAgIQCREfzzVyDTMcNME+gWnTCTANBgkqhkiG9w0BAQsFADCB +nDELMAkGA1UEBhMCVVMxIjAgBgNVBAoMGUFtYXpvbiBXZWIgU2VydmljZXMsIElu +Yy4xEzARBgNVBAsMCkFtYXpvbiBSRFMxCzAJBgNVBAgMAldBMTUwMwYDVQQDDCxB +bWF6b24gUkRTIGFwLXNvdXRoZWFzdC0yIFJvb3QgQ0EgUlNBMjA0OCBHMTEQMA4G +A1UEBwwHU2VhdHRsZTAgFw0yMTA1MjQyMDQyMzNaGA8yMDYxMDUyNDIxNDIzM1ow +gZwxCzAJBgNVBAYTAlVTMSIwIAYDVQQKDBlBbWF6b24gV2ViIFNlcnZpY2VzLCBJ +bmMuMRMwEQYDVQQLDApBbWF6b24gUkRTMQswCQYDVQQIDAJXQTE1MDMGA1UEAwws +QW1hem9uIFJEUyBhcC1zb3V0aGVhc3QtMiBSb290IENBIFJTQTIwNDggRzExEDAO +BgNVBAcMB1NlYXR0bGUwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDL +1MT6br3L/4Pq87DPXtcjlXN3cnbNk2YqRAZHJayStTz8VtsFcGPJOpk14geRVeVk +e9uKFHRbcyr/RM4owrJTj5X4qcEuATYZbo6ou/rW2kYzuWFZpFp7lqm0vasV4Z9F +fChlhwkNks0UbM3G+psCSMNSoF19ERunj7w2c4E62LwujkeYLvKGNepjnaH10TJL +2krpERd+ZQ4jIpObtRcMH++bTrvklc+ei8W9lqrVOJL+89v2piN3Ecdd389uphst +qQdb1BBVXbhUrtuGHgVf7zKqN1SkCoktoWxVuOprVWhSvr7akaWeq0UmlvbEsujU +vADqxGMcJFyCzxx3CkJjAgMBAAGjQjBAMA8GA1UdEwEB/wQFMAMBAf8wHQYDVR0O +BBYEFFk8UJmlhoxFT3PP12PvhvazHjT4MA4GA1UdDwEB/wQEAwIBhjANBgkqhkiG +9w0BAQsFAAOCAQEAfFtr2lGoWVXmWAsIo2NYre7kzL8Xb9Tx7desKxCCz5HOOvIr +8JMB1YK6A7IOvQsLJQ/f1UnKRh3X3mJZjKIywfrMSh0FiDf+rjcEzXxw2dGtUem4 +A+WMvIA3jwxnJ90OQj5rQ8bg3iPtE6eojzo9vWQGw/Vu48Dtw1DJo9210Lq/6hze +hPhNkFh8fMXNT7Q1Wz/TJqJElyAQGNOXhyGpHKeb0jHMMhsy5UNoW5hLeMS5ffao +TBFWEJ1gVfxIU9QRxSh+62m46JIg+dwDlWv8Aww14KgepspRbMqDuaM2cinoejv6 +t3dyOyHHrsOyv3ffZUKtQhQbQr+sUcL89lARsg== +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIID/jCCAuagAwIBAgIQSDZkikDRk3Yz12jYvk5noDANBgkqhkiG9w0BAQsFADCB +lzELMAkGA1UEBhMCVVMxIjAgBgNVBAoMGUFtYXpvbiBXZWIgU2VydmljZXMsIElu +Yy4xEzARBgNVBAsMCkFtYXpvbiBSRFMxCzAJBgNVBAgMAldBMTAwLgYDVQQDDCdB +bWF6b24gUkRTIGFwLWVhc3QtMiBSb290IENBIFJTQTIwNDggRzExEDAOBgNVBAcM +B1NlYXR0bGUwIBcNMjUwMjAxMDAxMDIxWhgPMjA2NTAyMDEwMTEwMjFaMIGXMQsw +CQYDVQQGEwJVUzEiMCAGA1UECgwZQW1hem9uIFdlYiBTZXJ2aWNlcywgSW5jLjET +MBEGA1UECwwKQW1hem9uIFJEUzELMAkGA1UECAwCV0ExMDAuBgNVBAMMJ0FtYXpv +biBSRFMgYXAtZWFzdC0yIFJvb3QgQ0EgUlNBMjA0OCBHMTEQMA4GA1UEBwwHU2Vh +dHRsZTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAKF7hTKGtJjPxPd5 +3reviptmh3QhN9tmcff4AIzU53BowyCGPgLJvw0JRSm4rgfDZAQ2rqQPbnWP9UBW +dZED3axk2bLWL6fddURHR5ckdb/Lv0NuNG/vdrG7l2V1jBasZNqAEVtASovdEHPX +Razz3DTedOSSKUttdJZtL4XbVLUMkB3YaT8a1UagQnuKO1pRiy9lvRUdP6hlhFW9 +qGduF/ZsTpFfxHp8VhOpAnMhwd4qHik8GLXdB9ajeKko84MU3tQn52GS4zvXWwY9 +dxeC0QjDcK2dG8m/43e01yDAonaVJPI3R849aNtiBA4U+8au0CNva5n6WNI5LQIO +XZmp5D8CAwEAAaNCMEAwDwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQUg6d1N4oJ +YOE1eAtYJexRSiFCpXEwDgYDVR0PAQH/BAQDAgGGMA0GCSqGSIb3DQEBCwUAA4IB +AQAOOBaSlMo+jhgz0ADaFuvFyaI+EVDB9GFT1TLpAmEK0gfNDI/RlY/n7hSKHJj0 +1ZeGfJ+9THD78QBuK7VyYlUkngYDdvEpg45ZEVKWGUtUmrhkikSsa5N8vioE3xg9 +MeSfgP98ykXVQH1kLSTUhLku8aLkwRdK96kSIKHRMUy41A9lK20X9Rc1dSsFJ/qe +TUFGCxwurKwR6geC+cw8aNjXHZlHZGNJPS+gRTizN1WYjEtJkjd5kQeNPgyppX26 +kRB9Vh54WJNIrs8PB9JsX9exu8f8m3hg5FFZONH5iBg0gdEMvbePP7JsjZD5BbjU +5C26Juxnw77pZbi0m7m3cmxL +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIID/zCCAuegAwIBAgIRAIJLTMpzGNxqHZ4t+c1MlCIwDQYJKoZIhvcNAQELBQAw +gZcxCzAJBgNVBAYTAlVTMSIwIAYDVQQKDBlBbWF6b24gV2ViIFNlcnZpY2VzLCBJ +bmMuMRMwEQYDVQQLDApBbWF6b24gUkRTMQswCQYDVQQIDAJXQTEwMC4GA1UEAwwn +QW1hem9uIFJEUyBhcC1lYXN0LTEgUm9vdCBDQSBSU0EyMDQ4IEcxMRAwDgYDVQQH +DAdTZWF0dGxlMCAXDTIxMDUyNTIxMzAzM1oYDzIwNjEwNTI1MjIzMDMzWjCBlzEL +MAkGA1UEBhMCVVMxIjAgBgNVBAoMGUFtYXpvbiBXZWIgU2VydmljZXMsIEluYy4x +EzARBgNVBAsMCkFtYXpvbiBSRFMxCzAJBgNVBAgMAldBMTAwLgYDVQQDDCdBbWF6 +b24gUkRTIGFwLWVhc3QtMSBSb290IENBIFJTQTIwNDggRzExEDAOBgNVBAcMB1Nl +YXR0bGUwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDtdHut0ZhJ9Nn2 +MpVafFcwHdoEzx06okmmhjJsNy4l9QYVeh0UUoek0SufRNMRF4d5ibzpgZol0Y92 +/qKWNe0jNxhEj6sXyHsHPeYtNBPuDMzThfbvsLK8z7pBP7vVyGPGuppqW/6m4ZBB +lcc9fsf7xpZ689iSgoyjiT6J5wlVgmCx8hFYc/uvcRtfd8jAHvheug7QJ3zZmIye +V4htOW+fRVWnBjf40Q+7uTv790UAqs0Zboj4Yil+hER0ibG62y1g71XcCyvcVpto +2/XW7Y9NCgMNqQ7fGN3wR1gjtSYPd7DO32LTzYhutyvfbpAZjsAHnoObmoljcgXI +QjfBcCFpAgMBAAGjQjBAMA8GA1UdEwEB/wQFMAMBAf8wHQYDVR0OBBYEFJI3aWLg +CS5xqU5WYVaeT5s8lpO0MA4GA1UdDwEB/wQEAwIBhjANBgkqhkiG9w0BAQsFAAOC +AQEAUwATpJOcGVOs3hZAgJwznWOoTzOVJKfrqBum7lvkVH1vBwxBl9CahaKj3ZOt +YYp2qJzhDUWludL164DL4ZjS6eRedLRviyy5cRy0581l1MxPWTThs27z+lCC14RL +PJZNVYYdl7Jy9Q5NsQ0RBINUKYlRY6OqGDySWyuMPgno2GPbE8aynMdKP+f6G/uE +YHOf08gFDqTsbyfa70ztgVEJaRooVf5JJq4UQtpDvVswW2reT96qi6tXPKHN5qp3 +3wI0I1Mp4ePmiBKku2dwYzPfrJK/pQlvu0Gu5lKOQ65QdotwLAAoaFqrf9za1yYs +INUkHLWIxDds+4OHNYcerGp5Dw== +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIICtzCCAj2gAwIBAgIQc48r2iRBCPPCj3oRSgZxujAKBggqhkjOPQQDAzCBmzEL +MAkGA1UEBhMCVVMxIjAgBgNVBAoMGUFtYXpvbiBXZWIgU2VydmljZXMsIEluYy4x +EzARBgNVBAsMCkFtYXpvbiBSRFMxCzAJBgNVBAgMAldBMTQwMgYDVQQDDCtBbWF6 +b24gUkRTIGFwLXNvdXRoZWFzdC03IFJvb3QgQ0EgRUNDMzg0IEcxMRAwDgYDVQQH +DAdTZWF0dGxlMCAXDTI0MDkxMjE1NTQ0MFoYDzIxMjQwOTEyMTY1NDQwWjCBmzEL +MAkGA1UEBhMCVVMxIjAgBgNVBAoMGUFtYXpvbiBXZWIgU2VydmljZXMsIEluYy4x +EzARBgNVBAsMCkFtYXpvbiBSRFMxCzAJBgNVBAgMAldBMTQwMgYDVQQDDCtBbWF6 +b24gUkRTIGFwLXNvdXRoZWFzdC03IFJvb3QgQ0EgRUNDMzg0IEcxMRAwDgYDVQQH +DAdTZWF0dGxlMHYwEAYHKoZIzj0CAQYFK4EEACIDYgAEVSBo1+i/T9w0C7C1qdnl +DfUXpFa+x4QYZvwLtt6m9L96k5irB7Wlw5168uTBW/ssRbv067PBnQEdZfI3iLKK +xWSpDFZN11tRneyDXag/fj1MCzBQ25WG+BitQdbzzuYuo0IwQDAPBgNVHRMBAf8E +BTADAQH/MB0GA1UdDgQWBBTiqTOsp/NisMSxMzARIGIKFcol4TAOBgNVHQ8BAf8E +BAMCAYYwCgYIKoZIzj0EAwMDaAAwZQIxAJlFgjECsVieUHNKE2Cmbj1wujERebHM +YNqCdnO//DeQ6Rh4SJkNaWB9LRmrmsdvIwIwf2iyQNetoO2JWNt7gzdRjO+7dF2+ +/LdagQCVp4R2N1Q6xzttRqZEK0lyAd0t7wlX +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIIGCTCCA/GgAwIBAgIRAIO6ldra1KZvNWJ0TA1ihXEwDQYJKoZIhvcNAQEMBQAw +gZwxCzAJBgNVBAYTAlVTMSIwIAYDVQQKDBlBbWF6b24gV2ViIFNlcnZpY2VzLCBJ +bmMuMRMwEQYDVQQLDApBbWF6b24gUkRTMQswCQYDVQQIDAJXQTE1MDMGA1UEAwws +QW1hem9uIFJEUyBhcC1zb3V0aGVhc3QtMSBSb290IENBIFJTQTQwOTYgRzExEDAO +BgNVBAcMB1NlYXR0bGUwIBcNMjEwNTIxMjE0NTA1WhgPMjEyMTA1MjEyMjQ1MDVa +MIGcMQswCQYDVQQGEwJVUzEiMCAGA1UECgwZQW1hem9uIFdlYiBTZXJ2aWNlcywg +SW5jLjETMBEGA1UECwwKQW1hem9uIFJEUzELMAkGA1UECAwCV0ExNTAzBgNVBAMM +LEFtYXpvbiBSRFMgYXAtc291dGhlYXN0LTEgUm9vdCBDQSBSU0E0MDk2IEcxMRAw +DgYDVQQHDAdTZWF0dGxlMIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEA +sDN52Si9pFSyZ1ruh3xAN0nVqEs960o2IK5CPu/ZfshFmzAwnx/MM8EHt/jMeZtj +SM58LADAsNDL01ELpFZATjgZQ6xNAyXRXE7RiTRUvNkK7O3o2qAGbLnJq/UqF7Sw +LRnB8V6hYOv+2EjVnohtGCn9SUFGZtYDjWXsLd4ML4Zpxv0a5LK7oEC7AHzbUR7R +jsjkrXqSv7GE7bvhSOhMkmgxgj1F3J0b0jdQdtyyj109aO0ATUmIvf+Bzadg5AI2 +A9UA+TUcGeebhpHu8AP1Hf56XIlzPpaQv3ZJ4vzoLaVNUC7XKzAl1dlvCl7Klg/C +84qmbD/tjZ6GHtzpLKgg7kQEV7mRoXq8X4wDX2AFPPQl2fv+Kbe+JODqm5ZjGegm +uskABBi8IFv1hYx9jEulZPxC6uD/09W2+niFm3pirnlWS83BwVDTUBzF+CooUIMT +jhWkIIZGDDgMJTzouBHfoSJtS1KpUZi99m2WyVs21MNKHeWAbs+zmI6TO5iiMC+T +uB8spaOiHFO1573Fmeer4sy3YA6qVoqVl6jjTQqOdy3frAMbCkwH22/crV8YA+08 +hLeHXrMK+6XUvU+EtHAM3VzcrLbuYJUI2XJbzTj5g0Eb8I8JWsHvWHR5K7Z7gceR +78AzxQmoGEfV6KABNWKsgoCQnfb1BidDJIe3BsI0A6UCAwEAAaNCMEAwDwYDVR0T +AQH/BAUwAwEB/zAdBgNVHQ4EFgQUABp0MlB14MSHgAcuNSOhs3MOlUcwDgYDVR0P +AQH/BAQDAgGGMA0GCSqGSIb3DQEBDAUAA4ICAQCv4CIOBSQi/QR9NxdRgVAG/pAh +tFJhV7OWb/wqwsNKFDtg6tTxwaahdCfWpGWId15OUe7G9LoPiKiwM9C92n0ZeHRz +4ewbrQVo7Eu1JI1wf0rnZJISL72hVYKmlvaWaacHhWxvsbKLrB7vt6Cknxa+S993 +Kf8i2Psw8j5886gaxhiUtzMTBwoDWak8ZaK7m3Y6C6hXQk08+3pnIornVSFJ9dlS +PAqt5UPwWmrEfF+0uIDORlT+cvrAwgSp7nUF1q8iasledycZ/BxFgQqzNwnkBDwQ +Z/aM52ArGsTzfMhkZRz9HIEhz1/0mJw8gZtDVQroD8778h8zsx2SrIz7eWQ6uWsD +QEeSWXpcheiUtEfzkDImjr2DLbwbA23c9LoexUD10nwohhoiQQg77LmvBVxeu7WU +E63JqaYUlOLOzEmNJp85zekIgR8UTkO7Gc+5BD7P4noYscI7pPOL5rP7YLg15ZFi +ega+G53NTckRXz4metsd8XFWloDjZJJq4FfD60VuxgXzoMNT9wpFTNSH42PR2s9L +I1vcl3w8yNccs9se2utM2nLsItZ3J0m/+QSRiw9hbrTYTcM9sXki0DtH2kyIOwYf +lOrGJDiYOIrXSQK36H0gQ+8omlrUTvUj4msvkXuQjlfgx6sgp2duOAfnGxE7uHnc +UhnJzzoe6M+LfGHkVQ== +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIICuDCCAj2gAwIBAgIQSAG6j2WHtWUUuLGJTPb1nTAKBggqhkjOPQQDAzCBmzEL +MAkGA1UEBhMCVVMxIjAgBgNVBAoMGUFtYXpvbiBXZWIgU2VydmljZXMsIEluYy4x +EzARBgNVBAsMCkFtYXpvbiBSRFMxCzAJBgNVBAgMAldBMTQwMgYDVQQDDCtBbWF6 +b24gUkRTIGFwLW5vcnRoZWFzdC0yIFJvb3QgQ0EgRUNDMzg0IEcxMRAwDgYDVQQH +DAdTZWF0dGxlMCAXDTIxMDUyMDE2MzgyNloYDzIxMjEwNTIwMTczODI2WjCBmzEL +MAkGA1UEBhMCVVMxIjAgBgNVBAoMGUFtYXpvbiBXZWIgU2VydmljZXMsIEluYy4x +EzARBgNVBAsMCkFtYXpvbiBSRFMxCzAJBgNVBAgMAldBMTQwMgYDVQQDDCtBbWF6 +b24gUkRTIGFwLW5vcnRoZWFzdC0yIFJvb3QgQ0EgRUNDMzg0IEcxMRAwDgYDVQQH +DAdTZWF0dGxlMHYwEAYHKoZIzj0CAQYFK4EEACIDYgAE2eqwU4FOzW8RV1W381Bd +olhDOrqoMqzWli21oDUt7y8OnXM/lmAuOS6sr8Nt61BLVbONdbr+jgCYw75KabrK +ZGg3siqvMOgabIKkKuXO14wtrGyGDt7dnKXg5ERGYOZlo0IwQDAPBgNVHRMBAf8E +BTADAQH/MB0GA1UdDgQWBBS1Acp2WYxOcblv5ikZ3ZIbRCCW+zAOBgNVHQ8BAf8E +BAMCAYYwCgYIKoZIzj0EAwMDaQAwZgIxAJL84J08PBprxmsAKPTotBuVI3MyW1r8 +xQ0i8lgCQUf8GcmYjQ0jI4oZyv+TuYJAcwIxAP9Xpzq0Docxb+4N1qVhpiOfWt1O +FnemFiy9m1l+wv6p3riQMPV7mBVpklmijkIv3Q== +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIIECTCCAvGgAwIBAgIRALZLcqCVIJ25maDPE3sbPCIwDQYJKoZIhvcNAQELBQAw +gZwxCzAJBgNVBAYTAlVTMSIwIAYDVQQKDBlBbWF6b24gV2ViIFNlcnZpY2VzLCBJ +bmMuMRMwEQYDVQQLDApBbWF6b24gUkRTMQswCQYDVQQIDAJXQTE1MDMGA1UEAwws +QW1hem9uIFJEUyBhcC1zb3V0aGVhc3QtMSBSb290IENBIFJTQTIwNDggRzExEDAO +BgNVBAcMB1NlYXR0bGUwIBcNMjEwNTIxMjEzOTM5WhgPMjA2MTA1MjEyMjM5Mzla +MIGcMQswCQYDVQQGEwJVUzEiMCAGA1UECgwZQW1hem9uIFdlYiBTZXJ2aWNlcywg +SW5jLjETMBEGA1UECwwKQW1hem9uIFJEUzELMAkGA1UECAwCV0ExNTAzBgNVBAMM +LEFtYXpvbiBSRFMgYXAtc291dGhlYXN0LTEgUm9vdCBDQSBSU0EyMDQ4IEcxMRAw +DgYDVQQHDAdTZWF0dGxlMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA +ypKc+6FfGx6Gl6fQ78WYS29QoKgQiur58oxR3zltWeg5fqh9Z85K5S3UbRSTqWWu +Xcfnkz0/FS07qHX+nWAGU27JiQb4YYqhjZNOAq8q0+ptFHJ6V7lyOqXBq5xOzO8f ++0DlbJSsy7GEtJp7d7QCM3M5KVY9dENVZUKeJwa8PC5StvwPx4jcLeZRJC2rAVDG +SW7NAInbATvr9ssSh03JqjXb+HDyywiqoQ7EVLtmtXWimX+0b3/2vhqcH5jgcKC9 +IGFydrjPbv4kwMrKnm6XlPZ9L0/3FMzanXPGd64LQVy51SI4d5Xymn0Mw2kMX8s6 +Nf05OsWcDzJ1n6/Q1qHSxQIDAQABo0IwQDAPBgNVHRMBAf8EBTADAQH/MB0GA1Ud +DgQWBBRmaIc8eNwGP7i6P7AJrNQuK6OpFzAOBgNVHQ8BAf8EBAMCAYYwDQYJKoZI +hvcNAQELBQADggEBAIBeHfGwz3S2zwIUIpqEEI5/sMySDeS+3nJR+woWAHeO0C8i +BJdDh+kzzkP0JkWpr/4NWz84/IdYo1lqASd1Kopz9aT1+iROXaWr43CtbzjXb7/X +Zv7eZZFC8/lS5SROq42pPWl4ekbR0w8XGQElmHYcWS41LBfKeHCUwv83ATF0XQ6I +4t+9YSqZHzj4vvedrvcRInzmwWJaal9s7Z6GuwTGmnMsN3LkhZ+/GD6oW3pU/Pyh +EtWqffjsLhfcdCs3gG8x9BbkcJPH5aPAVkPn4wc8wuXg6xxb9YGsQuY930GWTYRf +schbgjsuqznW4HHakq4WNhs1UdTSTKkRdZz7FUQ= +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIIEDzCCAvegAwIBAgIRAM2zAbhyckaqRim63b+Tib8wDQYJKoZIhvcNAQELBQAw +gZ8xCzAJBgNVBAYTAlVTMSIwIAYDVQQKDBlBbWF6b24gV2ViIFNlcnZpY2VzLCBJ +bmMuMRMwEQYDVQQLDApBbWF6b24gUkRTMQswCQYDVQQIDAJXQTE4MDYGA1UEAwwv +QW1hem9uIFJEUyBQcmV2aWV3IHVzLWVhc3QtMiBSb290IENBIFJTQTIwNDggRzEx +EDAOBgNVBAcMB1NlYXR0bGUwIBcNMjEwNTE4MjA0OTQ1WhgPMjA2MTA1MTgyMTQ5 +NDVaMIGfMQswCQYDVQQGEwJVUzEiMCAGA1UECgwZQW1hem9uIFdlYiBTZXJ2aWNl +cywgSW5jLjETMBEGA1UECwwKQW1hem9uIFJEUzELMAkGA1UECAwCV0ExODA2BgNV +BAMML0FtYXpvbiBSRFMgUHJldmlldyB1cy1lYXN0LTIgUm9vdCBDQSBSU0EyMDQ4 +IEcxMRAwDgYDVQQHDAdTZWF0dGxlMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIB +CgKCAQEA1ybjQMH1MkbvfKsWJaCTXeCSN1SG5UYid+Twe+TjuSqaXWonyp4WRR5z +tlkqq+L2MWUeQQAX3S17ivo/t84mpZ3Rla0cx39SJtP3BiA2BwfUKRjhPwOjmk7j +3zrcJjV5k1vSeLNOfFFSlwyDiVyLAE61lO6onBx+cRjelu0egMGq6WyFVidTdCmT +Q9Zw3W6LTrnPvPmEyjHy2yCHzH3E50KSd/5k4MliV4QTujnxYexI2eR8F8YQC4m3 +DYjXt/MicbqA366SOoJA50JbgpuVv62+LSBu56FpzY12wubmDZsdn4lsfYKiWxUy +uc83a2fRXsJZ1d3whxrl20VFtLFHFQIDAQABo0IwQDAPBgNVHRMBAf8EBTADAQH/ +MB0GA1UdDgQWBBRC0ytKmDYbfz0Bz0Psd4lRQV3aNTAOBgNVHQ8BAf8EBAMCAYYw +DQYJKoZIhvcNAQELBQADggEBAGv8qZu4uaeoF6zsbumauz6ea6tdcWt+hGFuwGrb +tRbI85ucAmVSX06x59DJClsb4MPhL1XmqO3RxVMIVVfRwRHWOsZQPnXm8OYQ2sny +rYuFln1COOz1U/KflZjgJmxbn8x4lYiTPZRLarG0V/OsCmnLkQLPtEl/spMu8Un7 +r3K8SkbWN80gg17Q8EV5mnFwycUx9xsTAaFItuG0en9bGsMgMmy+ZsDmTRbL+lcX +Fq8r4LT4QjrFz0shrzCwuuM4GmcYtBSxlacl+HxYEtAs5k10tmzRf6OYlY33tGf6 +1tkYvKryxDPF/EDgGp/LiBwx6ixYMBfISoYASt4V/ylAlHA= +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIICtTCCAjqgAwIBAgIRAK9BSZU6nIe6jqfODmuVctYwCgYIKoZIzj0EAwMwgZkx +CzAJBgNVBAYTAlVTMSIwIAYDVQQKDBlBbWF6b24gV2ViIFNlcnZpY2VzLCBJbmMu +MRMwEQYDVQQLDApBbWF6b24gUkRTMQswCQYDVQQIDAJXQTEyMDAGA1UEAwwpQW1h +em9uIFJEUyBjYS1jZW50cmFsLTEgUm9vdCBDQSBFQ0MzODQgRzExEDAOBgNVBAcM +B1NlYXR0bGUwIBcNMjEwNTIxMjIxMzA5WhgPMjEyMTA1MjEyMzEzMDlaMIGZMQsw +CQYDVQQGEwJVUzEiMCAGA1UECgwZQW1hem9uIFdlYiBTZXJ2aWNlcywgSW5jLjET +MBEGA1UECwwKQW1hem9uIFJEUzELMAkGA1UECAwCV0ExMjAwBgNVBAMMKUFtYXpv +biBSRFMgY2EtY2VudHJhbC0xIFJvb3QgQ0EgRUNDMzg0IEcxMRAwDgYDVQQHDAdT +ZWF0dGxlMHYwEAYHKoZIzj0CAQYFK4EEACIDYgAEUkEERcgxneT5H+P+fERcbGmf +bVx+M7rNWtgWUr6w+OBENebQA9ozTkeSg4c4M+qdYSObFqjxITdYxT1z/nHz1gyx +OKAhLjWu+nkbRefqy3RwXaWT680uUaAP6ccnkZOMo0IwQDAPBgNVHRMBAf8EBTAD +AQH/MB0GA1UdDgQWBBSN6fxlg0s5Wny08uRBYZcQ3TUoyzAOBgNVHQ8BAf8EBAMC +AYYwCgYIKoZIzj0EAwMDaQAwZgIxAORaz+MBVoFBTmZ93j2G2vYTwA6T5hWzBWrx +CrI54pKn5g6At56DBrkjrwZF5T1enAIxAJe/LZ9xpDkAdxDgGJFN8gZYLRWc0NRy +Rb4hihy5vj9L+w9uKc9VfEBIFuhT7Z3ljg== +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIIEADCCAuigAwIBAgIQB/57HSuaqUkLaasdjxUdPjANBgkqhkiG9w0BAQsFADCB +mDELMAkGA1UEBhMCVVMxIjAgBgNVBAoMGUFtYXpvbiBXZWIgU2VydmljZXMsIElu +Yy4xEzARBgNVBAsMCkFtYXpvbiBSRFMxCzAJBgNVBAgMAldBMTEwLwYDVQQDDChB +bWF6b24gUkRTIGFwLXNvdXRoLTEgUm9vdCBDQSBSU0EyMDQ4IEcxMRAwDgYDVQQH +DAdTZWF0dGxlMCAXDTIxMDUxOTE3NDAzNFoYDzIwNjEwNTE5MTg0MDM0WjCBmDEL +MAkGA1UEBhMCVVMxIjAgBgNVBAoMGUFtYXpvbiBXZWIgU2VydmljZXMsIEluYy4x +EzARBgNVBAsMCkFtYXpvbiBSRFMxCzAJBgNVBAgMAldBMTEwLwYDVQQDDChBbWF6 +b24gUkRTIGFwLXNvdXRoLTEgUm9vdCBDQSBSU0EyMDQ4IEcxMRAwDgYDVQQHDAdT +ZWF0dGxlMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAtbkaoVsUS76o +TgLFmcnaB8cswBk1M3Bf4IVRcwWT3a1HeJSnaJUqWHCJ+u3ip/zGVOYl0gN1MgBb +MuQRIJiB95zGVcIa6HZtx00VezDTr3jgGWRHmRjNVCCHGmxOZWvJjsIE1xavT/1j +QYV/ph4EZEIZ/qPq7e3rHohJaHDe23Z7QM9kbyqp2hANG2JtU/iUhCxqgqUHNozV +Zd0l5K6KnltZQoBhhekKgyiHqdTrH8fWajYl5seD71bs0Axowb+Oh0rwmrws3Db2 +Dh+oc2PwREnjHeca9/1C6J2vhY+V0LGaJmnnIuOANrslx2+bgMlyhf9j0Bv8AwSi +dSWsobOhNQIDAQABo0IwQDAPBgNVHRMBAf8EBTADAQH/MB0GA1UdDgQWBBQb7vJT +VciLN72yJGhaRKLn6Krn2TAOBgNVHQ8BAf8EBAMCAYYwDQYJKoZIhvcNAQELBQAD +ggEBAAxEj8N9GslReAQnNOBpGl8SLgCMTejQ6AW/bapQvzxrZrfVOZOYwp/5oV0f +9S1jcGysDM+DrmfUJNzWxq2Y586R94WtpH4UpJDGqZp+FuOVJL313te4609kopzO +lDdmd+8z61+0Au93wB1rMiEfnIMkOEyt7D2eTFJfJRKNmnPrd8RjimRDlFgcLWJA +3E8wca67Lz/G0eAeLhRHIXv429y8RRXDtKNNz0wA2RwURWIxyPjn1fHjA9SPDkeW +E1Bq7gZj+tBnrqz+ra3yjZ2blss6Ds3/uRY6NYqseFTZWmQWT7FolZEnT9vMUitW +I0VynUbShVpGf6946e0vgaaKw20= +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIID/jCCAuagAwIBAgIQGyUVTaVjYJvWhroVEiHPpDANBgkqhkiG9w0BAQsFADCB +lzELMAkGA1UEBhMCVVMxIjAgBgNVBAoMGUFtYXpvbiBXZWIgU2VydmljZXMsIElu +Yy4xEzARBgNVBAsMCkFtYXpvbiBSRFMxCzAJBgNVBAgMAldBMTAwLgYDVQQDDCdB +bWF6b24gUkRTIHVzLXdlc3QtMSBSb290IENBIFJTQTIwNDggRzExEDAOBgNVBAcM +B1NlYXR0bGUwIBcNMjEwNTE5MTkwNDA2WhgPMjA2MTA1MTkyMDA0MDZaMIGXMQsw +CQYDVQQGEwJVUzEiMCAGA1UECgwZQW1hem9uIFdlYiBTZXJ2aWNlcywgSW5jLjET +MBEGA1UECwwKQW1hem9uIFJEUzELMAkGA1UECAwCV0ExMDAuBgNVBAMMJ0FtYXpv +biBSRFMgdXMtd2VzdC0xIFJvb3QgQ0EgUlNBMjA0OCBHMTEQMA4GA1UEBwwHU2Vh +dHRsZTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBANhyXpJ0t4nigRDZ +EwNtFOem1rM1k8k5XmziHKDvDk831p7QsX9ZOxl/BT59Pu/P+6W6SvasIyKls1sW +FJIjFF+6xRQcpoE5L5evMgN/JXahpKGeQJPOX9UEXVW5B8yi+/dyUitFT7YK5LZA +MqWBN/LtHVPa8UmE88RCDLiKkqiv229tmwZtWT7nlMTTCqiAHMFcryZHx0pf9VPh +x/iPV8p2gBJnuPwcz7z1kRKNmJ8/cWaY+9w4q7AYlAMaq/rzEqDaN2XXevdpsYAK +TMMj2kji4x1oZO50+VPNfBl5ZgJc92qz1ocF95SAwMfOUsP8AIRZkf0CILJYlgzk +/6u6qZECAwEAAaNCMEAwDwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQUm5jfcS9o ++LwL517HpB6hG+PmpBswDgYDVR0PAQH/BAQDAgGGMA0GCSqGSIb3DQEBCwUAA4IB +AQAcQ6lsqxi63MtpGk9XK8mCxGRLCad51+MF6gcNz6i6PAqhPOoKCoFqdj4cEQTF +F8dCfa3pvfJhxV6RIh+t5FCk/y6bWT8Ls/fYKVo6FhHj57bcemWsw/Z0XnROdVfK +Yqbc7zvjCPmwPHEqYBhjU34NcY4UF9yPmlLOL8uO1JKXa3CAR0htIoW4Pbmo6sA4 +6P0co/clW+3zzsQ92yUCjYmRNeSbdXbPfz3K/RtFfZ8jMtriRGuO7KNxp8MqrUho +HK8O0mlSUxGXBZMNicfo7qY8FD21GIPH9w5fp5oiAl7lqFzt3E3sCLD3IiVJmxbf +fUwpGd1XZBBSdIxysRLM6j48 +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIICrTCCAjOgAwIBAgIQU+PAILXGkpoTcpF200VD/jAKBggqhkjOPQQDAzCBljEL +MAkGA1UEBhMCVVMxIjAgBgNVBAoMGUFtYXpvbiBXZWIgU2VydmljZXMsIEluYy4x +EzARBgNVBAsMCkFtYXpvbiBSRFMxCzAJBgNVBAgMAldBMS8wLQYDVQQDDCZBbWF6 +b24gUkRTIGFwLWVhc3QtMSBSb290IENBIEVDQzM4NCBHMTEQMA4GA1UEBwwHU2Vh +dHRsZTAgFw0yMTA1MjUyMTQ1MTFaGA8yMTIxMDUyNTIyNDUxMVowgZYxCzAJBgNV +BAYTAlVTMSIwIAYDVQQKDBlBbWF6b24gV2ViIFNlcnZpY2VzLCBJbmMuMRMwEQYD +VQQLDApBbWF6b24gUkRTMQswCQYDVQQIDAJXQTEvMC0GA1UEAwwmQW1hem9uIFJE +UyBhcC1lYXN0LTEgUm9vdCBDQSBFQ0MzODQgRzExEDAOBgNVBAcMB1NlYXR0bGUw +djAQBgcqhkjOPQIBBgUrgQQAIgNiAAT3tFKE8Kw1sGQAvNLlLhd8OcGhlc7MiW/s +NXm3pOiCT4vZpawKvHBzD76Kcv+ZZzHRxQEmG1/muDzZGlKR32h8AAj+NNO2Wy3d +CKTtYMiVF6Z2zjtuSkZQdjuQbe4eQ7qjQjBAMA8GA1UdEwEB/wQFMAMBAf8wHQYD +VR0OBBYEFAiSQOp16Vv0Ohpvqcbd2j5RmhYNMA4GA1UdDwEB/wQEAwIBhjAKBggq +hkjOPQQDAwNoADBlAjBVsi+5Ape0kOhMt/WFkANkslD4qXA5uqhrfAtH29Xzz2NV +tR7akiA771OaIGB/6xsCMQCZt2egCtbX7J0WkuZ2KivTh66jecJr5DHvAP4X2xtS +F/5pS+AUhcKTEGjI9jDH3ew= +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIICuDCCAj2gAwIBAgIQT5mGlavQzFHsB7hV6Mmy6TAKBggqhkjOPQQDAzCBmzEL +MAkGA1UEBhMCVVMxIjAgBgNVBAoMGUFtYXpvbiBXZWIgU2VydmljZXMsIEluYy4x +EzARBgNVBAsMCkFtYXpvbiBSRFMxCzAJBgNVBAgMAldBMTQwMgYDVQQDDCtBbWF6 +b24gUkRTIGFwLXNvdXRoZWFzdC0yIFJvb3QgQ0EgRUNDMzg0IEcxMRAwDgYDVQQH +DAdTZWF0dGxlMCAXDTIxMDUyNDIwNTAxNVoYDzIxMjEwNTI0MjE1MDE1WjCBmzEL +MAkGA1UEBhMCVVMxIjAgBgNVBAoMGUFtYXpvbiBXZWIgU2VydmljZXMsIEluYy4x +EzARBgNVBAsMCkFtYXpvbiBSRFMxCzAJBgNVBAgMAldBMTQwMgYDVQQDDCtBbWF6 +b24gUkRTIGFwLXNvdXRoZWFzdC0yIFJvb3QgQ0EgRUNDMzg0IEcxMRAwDgYDVQQH +DAdTZWF0dGxlMHYwEAYHKoZIzj0CAQYFK4EEACIDYgAEcm4BBBjYK7clwm0HJRWS +flt3iYwoJbIXiXn9c1y3E+Vb7bmuyKhS4eO8mwO4GefUcXObRfoHY2TZLhMJLVBQ +7MN2xDc0RtZNj07BbGD3VAIFRTDX0mH9UNYd0JQM3t/Oo0IwQDAPBgNVHRMBAf8E +BTADAQH/MB0GA1UdDgQWBBRrd5ITedfAwrGo4FA9UaDaGFK3rjAOBgNVHQ8BAf8E +BAMCAYYwCgYIKoZIzj0EAwMDaQAwZgIxAPBNqmVv1IIA3EZyQ6XuVf4gj79/DMO8 +bkicNS1EcBpUqbSuU4Zwt2BYc8c/t7KVOQIxAOHoWkoKZPiKyCxfMtJpCZySUG+n +sXgB/LOyWE5BJcXUfm+T1ckeNoWeUUMOLmnJjg== +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIIECTCCAvGgAwIBAgIRAJcDeinvdNrDQBeJ8+t38WQwDQYJKoZIhvcNAQELBQAw +gZwxCzAJBgNVBAYTAlVTMSIwIAYDVQQKDBlBbWF6b24gV2ViIFNlcnZpY2VzLCBJ +bmMuMRMwEQYDVQQLDApBbWF6b24gUkRTMQswCQYDVQQIDAJXQTE1MDMGA1UEAwws +QW1hem9uIFJEUyBhcC1zb3V0aGVhc3QtNCBSb290IENBIFJTQTIwNDggRzExEDAO +BgNVBAcMB1NlYXR0bGUwIBcNMjIwNTI1MTY0OTE2WhgPMjA2MjA1MjUxNzQ5MTZa +MIGcMQswCQYDVQQGEwJVUzEiMCAGA1UECgwZQW1hem9uIFdlYiBTZXJ2aWNlcywg +SW5jLjETMBEGA1UECwwKQW1hem9uIFJEUzELMAkGA1UECAwCV0ExNTAzBgNVBAMM +LEFtYXpvbiBSRFMgYXAtc291dGhlYXN0LTQgUm9vdCBDQSBSU0EyMDQ4IEcxMRAw +DgYDVQQHDAdTZWF0dGxlMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA +k8DBNkr9tMoIM0NHoFiO7cQfSX0cOMhEuk/CHt0fFx95IBytx7GHCnNzpM27O5z6 +x6iRhfNnx+B6CrGyCzOjxvPizneY+h+9zfvNz9jj7L1I2uYMuiNyOKR6FkHR46CT +1CiArfVLLPaTqgD/rQjS0GL2sLHS/0dmYipzynnZcs613XT0rAWdYDYgxDq7r/Yi +Xge5AkWQFkMUq3nOYDLCyGGfQqWKkwv6lZUHLCDKf+Y0Uvsrj8YGCI1O8mF0qPCQ +lmlfaDvbuBu1AV+aabmkvyFj3b8KRIlNLEtQ4N8KGYR2Jdb82S4YUGIOAt4wuuFt +1B7AUDLk3V/u+HTWiwfoLQIDAQABo0IwQDAPBgNVHRMBAf8EBTADAQH/MB0GA1Ud +DgQWBBSNpcjz6ArWBtAA+Gz6kyyZxrrgdDAOBgNVHQ8BAf8EBAMCAYYwDQYJKoZI +hvcNAQELBQADggEBAGJEd7UgOzHYIcQRSF7nSYyjLROyalaIV9AX4WXW/Cqlul1c +MblP5etDZm7A/thliZIWAuyqv2bNicmS3xKvNy6/QYi1YgxZyy/qwJ3NdFl067W0 +t8nGo29B+EVK94IPjzFHWShuoktIgp+dmpijB7wkTIk8SmIoe9yuY4+hzgqk+bo4 +ms2SOXSN1DoQ75Xv+YmztbnZM8MuWhL1T7hA4AMorzTQLJ9Pof8SpSdMHeDsHp0R +01jogNFkwy25nw7cL62nufSuH2fPYGWXyNDg+y42wKsKWYXLRgUQuDVEJ2OmTFMB +T0Vf7VuNijfIA9hkN2d3K53m/9z5WjGPSdOjGhg= +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIID/jCCAuagAwIBAgIQRiwspKyrO0xoxDgSkqLZczANBgkqhkiG9w0BAQsFADCB +lzELMAkGA1UEBhMCVVMxIjAgBgNVBAoMGUFtYXpvbiBXZWIgU2VydmljZXMsIElu +Yy4xEzARBgNVBAsMCkFtYXpvbiBSRFMxCzAJBgNVBAgMAldBMTAwLgYDVQQDDCdB +bWF6b24gUkRTIHVzLXdlc3QtMiBSb290IENBIFJTQTIwNDggRzExEDAOBgNVBAcM +B1NlYXR0bGUwIBcNMjEwNTI0MjE1OTAwWhgPMjA2MTA1MjQyMjU5MDBaMIGXMQsw +CQYDVQQGEwJVUzEiMCAGA1UECgwZQW1hem9uIFdlYiBTZXJ2aWNlcywgSW5jLjET +MBEGA1UECwwKQW1hem9uIFJEUzELMAkGA1UECAwCV0ExMDAuBgNVBAMMJ0FtYXpv +biBSRFMgdXMtd2VzdC0yIFJvb3QgQ0EgUlNBMjA0OCBHMTEQMA4GA1UEBwwHU2Vh +dHRsZTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAL53Jk3GsKiu+4bx +jDfsevWbwPCNJ3H08Zp7GWhvI3Tgi39opfHYv2ku2BKFjK8N2L6RvNPSR8yplv5j +Y0tK0U+XVNl8o0ibhqRDhbTuh6KL8CFINWYzAajuxFS+CF0U6c1Q3tXLBdALxA7l +FlXJ71QrP06W31kRe7kvgrvO7qWU3/OzUf9qYw4LSiR1/VkvvRCTqcVNw09clw/M +Jbw6FSgweN65M9j7zPbjGAXSHkXyxH1Erin2fa+B9PE4ZDgX9cp2C1DHewYJQL/g +SepwwcudVNRN1ibKH7kpMrgPnaNIVNx5sXVsTjk6q2ZqYw3SVHegltJpLy/cZReP +mlivF2kCAwEAAaNCMEAwDwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQUmTcQd6o1 +CuS65MjBrMwQ9JJjmBwwDgYDVR0PAQH/BAQDAgGGMA0GCSqGSIb3DQEBCwUAA4IB +AQAKSDSIzl956wVddPThf2VAzI8syw9ngSwsEHZvxVGHBvu5gg618rDyguVCYX9L +4Kw/xJrk6S3qxOS2ZDyBcOpsrBskgahDFIunzoRP3a18ARQVq55LVgfwSDQiunch +Bd05cnFGLoiLkR5rrkgYaP2ftn3gRBRaf0y0S3JXZ2XB3sMZxGxavYq9mfiEcwB0 +LMTMQ1NYzahIeG6Jm3LqRqR8HkzP/Ztq4dT2AtSLvFebbNMiWqeqT7OcYp94HTYT +zqrtaVdUg9bwyAUCDgy0GV9RHDIdNAOInU/4LEETovrtuBU7Z1q4tcHXvN6Hd1H8 +gMb0mCG5I393qW5hFsA/diFb +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIIGCTCCA/GgAwIBAgIRAIgSQsm7XtddDiXpo3j09qYwDQYJKoZIhvcNAQEMBQAw +gZwxCzAJBgNVBAYTAlVTMSIwIAYDVQQKDBlBbWF6b24gV2ViIFNlcnZpY2VzLCBJ +bmMuMRMwEQYDVQQLDApBbWF6b24gUkRTMQswCQYDVQQIDAJXQTE1MDMGA1UEAwws +QW1hem9uIFJEUyBhcC1zb3V0aGVhc3QtNyBSb290IENBIFJTQTQwOTYgRzExEDAO +BgNVBAcMB1NlYXR0bGUwIBcNMjQwOTEyMTU1NDM2WhgPMjEyNDA5MTIxNjU0MzZa +MIGcMQswCQYDVQQGEwJVUzEiMCAGA1UECgwZQW1hem9uIFdlYiBTZXJ2aWNlcywg +SW5jLjETMBEGA1UECwwKQW1hem9uIFJEUzELMAkGA1UECAwCV0ExNTAzBgNVBAMM +LEFtYXpvbiBSRFMgYXAtc291dGhlYXN0LTcgUm9vdCBDQSBSU0E0MDk2IEcxMRAw +DgYDVQQHDAdTZWF0dGxlMIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEA +jLfyuFmUeGd3WSDotJVQ+XjY20Yt75KWDFjXNLNZ7+/97RshOjZ0M3dZ+CcKmIQz +37vjb80kk0p+jeuaLxprWjc5kLJjaFZNPA8mxM/UmARvMvBRrO4uRRRQvYFyXqi+ +Frsl46t/+nyarL09ICx/1reZIzsOsI9BcDM0CK1hqQSwrIjOK2mXuHVfrufXjLnR +wZA2rOonQJJAXgOo1RvD11xmOUUIglP2ljAZZskxL+zU8d5k9Ed4HkGsOY3ywbP0 +/E+Fd33Gli3EF01wAbIaZL0vuW2rW4oxjh8QW7O7Sfnsr9fdNU2Tye1jSCxle5e3 +2Sfq+0Iw5TiT0KYTmcpcKzvljd1wwHdiNKeo7ZCVIer/lGl62PoAb2NWeLepk0IO +IwTZ+Dq76hs9XZJbYxWhXpVcT31b9e3++jROMPz7Mzi/wMg+RBWXgjDuuj1Z16sF +MFfK6QEmD8SOKL3GAKn7PBQxZea+j6iF7G1nGz1/Wfvzz7I8DocnAmQZabKzEnpR +LTEU2LZ86pkeLkM3uI9jm/VWeV4xLv/dC7trDkTUSHpJ4J5/ItMecdAlhCEJ2qM2 +x46OUbGYsf9gvacLkqQoS/XFL8R4bxR01URlHU+98yfODBCehulmnKOz3dQOth2c +sF9v2spYG2gmY3jg7N8suvPbzYKWmL7pVCQtqVOZPkkCAwEAAaNCMEAwDwYDVR0T +AQH/BAUwAwEB/zAdBgNVHQ4EFgQUy2Eg468T4F9w7gg9AxeZBj+gjKYwDgYDVR0P +AQH/BAQDAgGGMA0GCSqGSIb3DQEBDAUAA4ICAQCGLMyGrhwhqoDRtJ6gGx1SkJd4 +uxG3R4VFKS23JhDJKoDISjcCrVQu5Z3xSj157HmDnc/TEHgW/t1y472QOagzN2RD +9xgFBGfU1sHxKX79WBSf93L3alM+oE1tS+drDlBPV4gzxvCc+zvUYQKWjz/W6q9f +bdrk16J2/yOyli3XpEqf1o4c9FVnAbi9c3zdugfARskwypo2LHDuo8z+u/Ab7j1y +NZ8HtLg/hwVjfSoPLD8guYSZHXYn6ed7AmOUUJjlauWUfCCvU/F+Dk51JMLlVVK+ +y9ZgHXa8YDsGsCmpdgfC3MLSEVkx87mxj1rkFLI03q9i6L2BOWQtgd6NmvYd7pJx +cqx6bmSh43CscGDEngIB50XWqXd4QlVKmPPgDnd55szfnNQzzG9q8I9KTEv+B/Ck +apl2XxVAEUdb26Wi7RH+9Wms6HQeU4i7cDuWpm+EfZZmdjwvA4bs3Ox07SkenHqu +Ti6RJf1PiHoE9SHiFUiBnd++YsNByjGqn4Vxla1MlVdkaUMlvvc3/9oky4KfcRdQ +AK7L9cQqf7g6G4Ne5PNvosG3KaS009xSN1AAalTqS9eqrDYR2yZSixmUYqP57Jm5 +5ERI+pZi2aKpl0ONC7vKIyH+gxjsRPct4DKoxZnt4/KJYhINOlQz8i/j0jfqftmg +G1bYpbRRbfWoGHjGJA== +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIIECTCCAvGgAwIBAgIRAPQAvihfjBg/JDbj6U64K98wDQYJKoZIhvcNAQELBQAw +gZwxCzAJBgNVBAYTAlVTMSIwIAYDVQQKDBlBbWF6b24gV2ViIFNlcnZpY2VzLCBJ +bmMuMRMwEQYDVQQLDApBbWF6b24gUkRTMQswCQYDVQQIDAJXQTE1MDMGA1UEAwws +QW1hem9uIFJEUyBhcC1ub3J0aGVhc3QtMiBSb290IENBIFJTQTIwNDggRzExEDAO +BgNVBAcMB1NlYXR0bGUwIBcNMjEwNTIwMTYyODQxWhgPMjA2MTA1MjAxNzI4NDFa +MIGcMQswCQYDVQQGEwJVUzEiMCAGA1UECgwZQW1hem9uIFdlYiBTZXJ2aWNlcywg +SW5jLjETMBEGA1UECwwKQW1hem9uIFJEUzELMAkGA1UECAwCV0ExNTAzBgNVBAMM +LEFtYXpvbiBSRFMgYXAtbm9ydGhlYXN0LTIgUm9vdCBDQSBSU0EyMDQ4IEcxMRAw +DgYDVQQHDAdTZWF0dGxlMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA +vJ9lgyksCxkBlY40qOzI1TCj/Q0FVGuPL/Z1Mw2YN0l+41BDv0FHApjTUkIKOeIP +nwDwpXTa3NjYbk3cOZ/fpH2rYJ++Fte6PNDGPgKppVCUh6x3jiVZ1L7wOgnTdK1Q +Trw8440IDS5eLykRHvz8OmwvYDl0iIrt832V0QyOlHTGt6ZJ/aTQKl12Fy3QBLv7 +stClPzvHTrgWqVU6uidSYoDtzHbU7Vda7YH0wD9IUoMBf7Tu0rqcE4uH47s2XYkc +SdLEoOg/Ngs7Y9B1y1GCyj3Ux7hnyvCoRTw014QyNB7dTatFMDvYlrRDGG14KeiU +UL7Vo/+EejWI31eXNLw84wIDAQABo0IwQDAPBgNVHRMBAf8EBTADAQH/MB0GA1Ud +DgQWBBQkgTWFsNg6wA3HbbihDQ4vpt1E2zAOBgNVHQ8BAf8EBAMCAYYwDQYJKoZI +hvcNAQELBQADggEBAGz1Asiw7hn5WYUj8RpOCzpE0h/oBZcnxP8wulzZ5Xd0YxWO +0jYUcUk3tTQy1QvoY+Q5aCjg6vFv+oFBAxkib/SmZzp4xLisZIGlzpJQuAgRkwWA +6BVMgRS+AaOMQ6wKPgz1x4v6T0cIELZEPq3piGxvvqkcLZKdCaeC3wCS6sxuafzZ +4qA3zMwWuLOzRftgX2hQto7d/2YkRXga7jSvQl3id/EI+xrYoH6zIWgjdU1AUaNq +NGT7DIo47vVMfnd9HFZNhREsd4GJE83I+JhTqIxiKPNxrKgESzyADmNPt0gXDnHo +tbV1pMZz5HpJtjnP/qVZhEK5oB0tqlKPv9yx074= +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIICuTCCAj6gAwIBAgIRAKp1Rn3aL/g/6oiHVIXtCq8wCgYIKoZIzj0EAwMwgZsx +CzAJBgNVBAYTAlVTMSIwIAYDVQQKDBlBbWF6b24gV2ViIFNlcnZpY2VzLCBJbmMu +MRMwEQYDVQQLDApBbWF6b24gUkRTMQswCQYDVQQIDAJXQTE0MDIGA1UEAwwrQW1h +em9uIFJEUyBhcC1ub3J0aGVhc3QtMyBSb290IENBIEVDQzM4NCBHMTEQMA4GA1UE +BwwHU2VhdHRsZTAgFw0yMTA1MjQyMDMyMTdaGA8yMTIxMDUyNDIxMzIxN1owgZsx +CzAJBgNVBAYTAlVTMSIwIAYDVQQKDBlBbWF6b24gV2ViIFNlcnZpY2VzLCBJbmMu +MRMwEQYDVQQLDApBbWF6b24gUkRTMQswCQYDVQQIDAJXQTE0MDIGA1UEAwwrQW1h +em9uIFJEUyBhcC1ub3J0aGVhc3QtMyBSb290IENBIEVDQzM4NCBHMTEQMA4GA1UE +BwwHU2VhdHRsZTB2MBAGByqGSM49AgEGBSuBBAAiA2IABGTYWPILeBJXfcL3Dz4z +EWMUq78xB1HpjBwHoTURYfcMd5r96BTVG6yaUBWnAVCMeeD6yTG9a1eVGNhG14Hk +ZAEjgLiNB7RRbEG5JZ/XV7W/vODh09WCst2y9SLKsdgeAaNCMEAwDwYDVR0TAQH/ +BAUwAwEB/zAdBgNVHQ4EFgQUoE0qZHmDCDB+Bnm8GUa/evpfPwgwDgYDVR0PAQH/ +BAQDAgGGMAoGCCqGSM49BAMDA2kAMGYCMQCnil5MMwhY3qoXv0xvcKZGxGPaBV15 +0CCssCKn0oVtdJQfJQ3Jrf3RSaEyijXIJsoCMQC35iJi4cWoNX3N/qfgnHohW52O +B5dg0DYMqy5cNZ40+UcAanRMyqNQ6P7fy3umGco= +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIICtzCCAj2gAwIBAgIQPXnDTPegvJrI98qz8WxrMjAKBggqhkjOPQQDAzCBmzEL +MAkGA1UEBhMCVVMxIjAgBgNVBAoMGUFtYXpvbiBXZWIgU2VydmljZXMsIEluYy4x +EzARBgNVBAsMCkFtYXpvbiBSRFMxCzAJBgNVBAgMAldBMTQwMgYDVQQDDCtBbWF6 +b24gUkRTIEJldGEgdXMtZWFzdC0xIFJvb3QgQ0EgRUNDMzg0IEcxMRAwDgYDVQQH +DAdTZWF0dGxlMCAXDTIxMDUxODIxNDAxMloYDzIxMjEwNTE4MjI0MDEyWjCBmzEL +MAkGA1UEBhMCVVMxIjAgBgNVBAoMGUFtYXpvbiBXZWIgU2VydmljZXMsIEluYy4x +EzARBgNVBAsMCkFtYXpvbiBSRFMxCzAJBgNVBAgMAldBMTQwMgYDVQQDDCtBbWF6 +b24gUkRTIEJldGEgdXMtZWFzdC0xIFJvb3QgQ0EgRUNDMzg0IEcxMRAwDgYDVQQH +DAdTZWF0dGxlMHYwEAYHKoZIzj0CAQYFK4EEACIDYgAEI0sR7gwutK5AB46hM761 +gcLTGBIYlURSEoM1jcBwy56CL+3CJKZwLLyJ7qoOKfWbu5GsVLUTWS8MV6Nw33cx +2KQD2svb694wi+Px2f4n9+XHkEFQw8BbiodDD7RZA70fo0IwQDAPBgNVHRMBAf8E +BTADAQH/MB0GA1UdDgQWBBTQSioOvnVLEMXwNSDg+zgln/vAkjAOBgNVHQ8BAf8E +BAMCAYYwCgYIKoZIzj0EAwMDaAAwZQIxAMwu1hqm5Bc98uE/E0B5iMYbBQ4kpMxO +tP8FTfz5UR37HUn26nXE0puj6S/Ffj4oJgIwXI7s2c26tFQeqzq6u3lrNJHp5jC9 +Uxlo/hEJOLoDj5jnpxo8dMAtCNoQPaHdfL0P +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIIF/jCCA+agAwIBAgIQEM1pS+bWfBJeu/6j1yIIFzANBgkqhkiG9w0BAQwFADCB +lzELMAkGA1UEBhMCVVMxIjAgBgNVBAoMGUFtYXpvbiBXZWIgU2VydmljZXMsIElu +Yy4xEzARBgNVBAsMCkFtYXpvbiBSRFMxCzAJBgNVBAgMAldBMTAwLgYDVQQDDCdB +bWF6b24gUkRTIGNhLXdlc3QtMSBSb290IENBIFJTQTQwOTYgRzExEDAOBgNVBAcM +B1NlYXR0bGUwIBcNMjMwOTE5MjIwMTM5WhgPMjEyMzA5MTkyMzAxMzlaMIGXMQsw +CQYDVQQGEwJVUzEiMCAGA1UECgwZQW1hem9uIFdlYiBTZXJ2aWNlcywgSW5jLjET +MBEGA1UECwwKQW1hem9uIFJEUzELMAkGA1UECAwCV0ExMDAuBgNVBAMMJ0FtYXpv +biBSRFMgY2Etd2VzdC0xIFJvb3QgQ0EgUlNBNDA5NiBHMTEQMA4GA1UEBwwHU2Vh +dHRsZTCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBAK2Pyp8p5z6HnlGB +daOj78gZ3ABufxnBFiu5NdFiGoMrS+eY//xxr2iKbnynJAzjmn5A6VKMNxtbuYIZ +WKAzDb/HrWlIYD2w7ZVBXpylfPhiz3jLNsl03WdPNnEruCcivhY2QMewEVtzjPU0 +ofdbZlO2KpF3biv1gjPuIuE7AUyQAbWnWTlrzETAVWLboJJRRqxASSkFUHNLXod7 +ow02FwlAhcnCp9gSe1SKRDrpvvEvYQBAFB7owfnoQzOGDdd87RGyYfyuW8aFI2Z0 +LHNvsA0dTafO4Rh986c72kDL7ijICQdr5OTgZR2OnuESLk1DSK4xYJ4fA6jb5dJ5 ++xsI6tCPykWCW98aO/pha35OsrVNifL/5cH5pdv/ecgQGdffJB+Vdj6f/ZMwR6s/ +Rm37cQ9l3tU8eu/qpzsFjLq1ZUzDaVDWgMW9t49+q/zjhdmbPOabZDao7nHXrVRw +rwPHWCmEY4OmH6ikEKQW3AChFjOdSg4me/J0Jr5l5jKggLPHWbNLRO8qTTK6N8qk +ui3aJDi+XQfsTPARXIw4UFErArNImTsoZVyqfX7I4shp0qZbEhP6kRAbfPljw5kW +Yat7ZlXqDanjsreqbLTaOU10P0rC0/4Ctv5cLSKCrzRLWtpXxhKa2wJTQ74G6fAZ +1oUA79qg3F8nyM+ZzDsfNI854+PNAgMBAAGjQjBAMA8GA1UdEwEB/wQFMAMBAf8w +HQYDVR0OBBYEFLRWiDabEQZNkzEPUCr1ZVJV6xpwMA4GA1UdDwEB/wQEAwIBhjAN +BgkqhkiG9w0BAQwFAAOCAgEATkVVzkkGBjEtLGDtERi+fSpIV0MxwAsA4PAeBBmb +myxo90jz6kWkKM1Wm4BkZM8/mq5VbxPef1kxHfb5CHksCL6SgG5KujfIvht+KT2a +MRJB+III3CbcTy0HtwCX5AlPIbXWydhQFoJTW/OkpecUWoyFM6SqYeYZx1itJpxl +sXshLjYOvw+QgvxRsDxqUfkcaC/N2yhu/30Zo2P8msJfAFry2UmA/TBrWOQKVQxl +Ee/yWgp4U/bC/GZnjWnWDTwkRFGQtI4wjxbVuX6V4FTLCT7kIoHBhG+zOSduJRn3 +Axej7gkEXEVc/PAnwp/kSJ/b0/JONLWdjGUFkyiMn1yJlhJ2sg39vepBN5r6yVYU +nJWoZAuupRpoIKfmC3/cZanXqYbYl4yxzX/PMB4kAACfdxGxLawjnnBjSzaWokXs +YVh2TjWpUMwLOi0RB2mtPUjHdDLKtjOTZ1zHZnR/wVp9BmVI1BXYnz5PAqU5XqeD +EmanyaAuFCeyol1EtbQhgtysThQ+vwYAXMm2iKzJxq0hik8wyG8X55FhnGEOGV3u +xxq7odd3/8BXkc3dGdBPQtH+k5glaQyPnAsLVAIUvyzTmy58saL+nJnQY4mmRrwV +1jJA7nnkaklI/L5fvfCg0W+TMinCOAGd+GQ4hK2SAsJLtcqiBgPf2wJHO8wiwUh9 +Luw= +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIICrjCCAjWgAwIBAgIQGKVv+5VuzEZEBzJ+bVfx2zAKBggqhkjOPQQDAzCBlzEL +MAkGA1UEBhMCVVMxIjAgBgNVBAoMGUFtYXpvbiBXZWIgU2VydmljZXMsIEluYy4x +EzARBgNVBAsMCkFtYXpvbiBSRFMxCzAJBgNVBAgMAldBMTAwLgYDVQQDDCdBbWF6 +b24gUkRTIGFwLXNvdXRoLTEgUm9vdCBDQSBFQ0MzODQgRzExEDAOBgNVBAcMB1Nl +YXR0bGUwIBcNMjEwNTE5MTc1MDU5WhgPMjEyMTA1MTkxODUwNTlaMIGXMQswCQYD +VQQGEwJVUzEiMCAGA1UECgwZQW1hem9uIFdlYiBTZXJ2aWNlcywgSW5jLjETMBEG +A1UECwwKQW1hem9uIFJEUzELMAkGA1UECAwCV0ExMDAuBgNVBAMMJ0FtYXpvbiBS +RFMgYXAtc291dGgtMSBSb290IENBIEVDQzM4NCBHMTEQMA4GA1UEBwwHU2VhdHRs +ZTB2MBAGByqGSM49AgEGBSuBBAAiA2IABMqdLJ0tZF/DGFZTKZDrGRJZID8ivC2I +JRCYTWweZKCKSCAzoiuGGHzJhr5RlLHQf/QgmFcgXsdmO2n3CggzhA4tOD9Ip7Lk +P05eHd2UPInyPCHRgmGjGb0Z+RdQ6zkitKNCMEAwDwYDVR0TAQH/BAUwAwEB/zAd +BgNVHQ4EFgQUC1yhRgVqU5bR8cGzOUCIxRpl4EYwDgYDVR0PAQH/BAQDAgGGMAoG +CCqGSM49BAMDA2cAMGQCMG0c/zLGECRPzGKJvYCkpFTCUvdP4J74YP0v/dPvKojL +t/BrR1Tg4xlfhaib7hPc7wIwFvgqHes20CubQnZmswbTKLUrgSUW4/lcKFpouFd2 +t2/ewfi/0VhkeUW+IiHhOMdU +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIIGCTCCA/GgAwIBAgIRAOXxJuyXVkbfhZCkS/dOpfEwDQYJKoZIhvcNAQEMBQAw +gZwxCzAJBgNVBAYTAlVTMSIwIAYDVQQKDBlBbWF6b24gV2ViIFNlcnZpY2VzLCBJ +bmMuMRMwEQYDVQQLDApBbWF6b24gUkRTMQswCQYDVQQIDAJXQTE1MDMGA1UEAwws +QW1hem9uIFJEUyBhcC1ub3J0aGVhc3QtMSBSb290IENBIFJTQTQwOTYgRzExEDAO +BgNVBAcMB1NlYXR0bGUwIBcNMjEwNTI1MjE1OTEwWhgPMjEyMTA1MjUyMjU5MTBa +MIGcMQswCQYDVQQGEwJVUzEiMCAGA1UECgwZQW1hem9uIFdlYiBTZXJ2aWNlcywg +SW5jLjETMBEGA1UECwwKQW1hem9uIFJEUzELMAkGA1UECAwCV0ExNTAzBgNVBAMM +LEFtYXpvbiBSRFMgYXAtbm9ydGhlYXN0LTEgUm9vdCBDQSBSU0E0MDk2IEcxMRAw +DgYDVQQHDAdTZWF0dGxlMIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEA +xiP4RDYm4tIS12hGgn1csfO8onQDmK5SZDswUpl0HIKXOUVVWkHNlINkVxbdqpqH +FhbyZmNN6F/EWopotMDKe1B+NLrjNQf4zefv2vyKvPHJXhxoKmfyuTd5Wk8k1F7I +lNwLQzznB+ElhrLIDJl9Ro8t31YBBNFRGAGEnxyACFGcdkjlsa52UwfYrwreEg2l +gW5AzqHgjFfj9QRLydeU/n4bHm0F1adMsV7P3rVwilcUlqsENDwXnWyPEyv3sw6F +wNemLEs1129mB77fwvySb+lLNGsnzr8w4wdioZ74co+T9z2ca+eUiP+EQccVw1Is +D4Fh57IjPa6Wuc4mwiUYKkKY63+38aCfEWb0Qoi+zW+mE9nek6MOQ914cN12u5LX +dBoYopphRO5YmubSN4xcBy405nIdSdbrAVWwxXnVVyjqjknmNeqQsPZaxAhdoKhV +AqxNr8AUAdOAO6Sz3MslmcLlDXFihrEEOeUbpg/m1mSUUHGbu966ajTG1FuEHHwS +7WB52yxoJo/tHvt9nAWnh3uH5BHmS8zn6s6CGweWKbX5yICnZ1QFR1e4pogxX39v +XD6YcNOO+Vn+HY4nXmjgSYVC7l+eeP8eduMg1xJujzjrbmrXU+d+cBObgdTOAlpa +JFHaGwYw1osAwPCo9cZ2f04yitBfj9aPFia8ASKldakCAwEAAaNCMEAwDwYDVR0T +AQH/BAUwAwEB/zAdBgNVHQ4EFgQUqKS+ltlior0SyZKYAkJ/efv55towDgYDVR0P +AQH/BAQDAgGGMA0GCSqGSIb3DQEBDAUAA4ICAQAdElvp8bW4B+Cv+1WSN87dg6TN +wGyIjJ14/QYURgyrZiYpUmZpj+/pJmprSWXu4KNyqHftmaidu7cdjL5nCAvAfnY5 +/6eDDbX4j8Gt9fb/6H9y0O0dn3mUPSEKG0crR+JRFAtPhn/2FNvst2P82yguWLv0 +pHjHVUVcq+HqDMtUIJsTPYjSh9Iy77Q6TOZKln9dyDOWJpCSkiUWQtMAKbCSlvzd +zTs/ahqpT+zLfGR1SR+T3snZHgQnbnemmz/XtlKl52NxccARwfcEEKaCRQyGq/pR +0PVZasyJS9JY4JfQs4YOdeOt4UMZ8BmW1+BQWGSkkb0QIRl8CszoKofucAlqdPcO +IT/ZaMVhI580LFGWiQIizWFskX6lqbCyHqJB3LDl8gJISB5vNTHOHpvpMOMs5PYt +cRl5Mrksx5MKMqG7y5R734nMlZxQIHjL5FOoOxTBp9KeWIL/Ib89T2QDaLw1SQ+w +ihqWBJ4ZdrIMWYpP3WqM+MXWk7WAem+xsFJdR+MDgOOuobVQTy5dGBlPks/6gpjm +rO9TjfQ36ppJ3b7LdKUPeRfnYmlR5RU4oyYJ//uLbClI443RZAgxaCXX/nyc12lr +eVLUMNF2abLX4/VF63m2/Z9ACgMRfqGshPssn1NN33OonrotQoj4S3N9ZrjvzKt8 +iHcaqd60QKpfiH2A3A== +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIICrTCCAjOgAwIBAgIQOsXM3iPpLzFtjuRCpLsrlDAKBggqhkjOPQQDAzCBljEL +MAkGA1UEBhMCVVMxIjAgBgNVBAoMGUFtYXpvbiBXZWIgU2VydmljZXMsIEluYy4x +EzARBgNVBAsMCkFtYXpvbiBSRFMxCzAJBgNVBAgMAldBMS8wLQYDVQQDDCZBbWF6 +b24gUkRTIGFwLWVhc3QtMiBSb290IENBIEVDQzM4NCBHMTEQMA4GA1UEBwwHU2Vh +dHRsZTAgFw0yNTAyMDEwMDEwMzFaGA8yMTI1MDIwMTAxMTAzMVowgZYxCzAJBgNV +BAYTAlVTMSIwIAYDVQQKDBlBbWF6b24gV2ViIFNlcnZpY2VzLCBJbmMuMRMwEQYD +VQQLDApBbWF6b24gUkRTMQswCQYDVQQIDAJXQTEvMC0GA1UEAwwmQW1hem9uIFJE +UyBhcC1lYXN0LTIgUm9vdCBDQSBFQ0MzODQgRzExEDAOBgNVBAcMB1NlYXR0bGUw +djAQBgcqhkjOPQIBBgUrgQQAIgNiAAQ+7GUElCbaFRRjg6gT4QTBuontLCF4sLso +/sHZ60qQI8SVEsDTF7C3zebh+VlNtPhgF1Le61HW86KlDQYf49pVN2guXBrdf3qb +RQCwSHSKs4S2bMyRMZ6xjwoGOrEKFEKjQjBAMA8GA1UdEwEB/wQFMAMBAf8wHQYD +VR0OBBYEFKc0lLMy1OaSxQ+rtD7tyY3bFlv5MA4GA1UdDwEB/wQEAwIBhjAKBggq +hkjOPQQDAwNoADBlAjEA5Rhu3YN+PyIHg3NXSw1DbowJIHNZgnieTD4BPkZLy4rs +8le5S3JjQQGyh+bsJf7RAjBPUMBF4CJDLE5nmetgXLlOl3oXwd/gHTls7YDr+R18 +cTBI0+RV4Z6yT5p1OF9AcnA= +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIICuDCCAj2gAwIBAgIQPaVGRuu86nh/ylZVCLB0MzAKBggqhkjOPQQDAzCBmzEL +MAkGA1UEBhMCVVMxIjAgBgNVBAoMGUFtYXpvbiBXZWIgU2VydmljZXMsIEluYy4x +EzARBgNVBAsMCkFtYXpvbiBSRFMxCzAJBgNVBAgMAldBMTQwMgYDVQQDDCtBbWF6 +b24gUkRTIGFwLW5vcnRoZWFzdC0xIFJvb3QgQ0EgRUNDMzg0IEcxMRAwDgYDVQQH +DAdTZWF0dGxlMCAXDTIxMDUyNTIyMDMxNloYDzIxMjEwNTI1MjMwMzE2WjCBmzEL +MAkGA1UEBhMCVVMxIjAgBgNVBAoMGUFtYXpvbiBXZWIgU2VydmljZXMsIEluYy4x +EzARBgNVBAsMCkFtYXpvbiBSRFMxCzAJBgNVBAgMAldBMTQwMgYDVQQDDCtBbWF6 +b24gUkRTIGFwLW5vcnRoZWFzdC0xIFJvb3QgQ0EgRUNDMzg0IEcxMRAwDgYDVQQH +DAdTZWF0dGxlMHYwEAYHKoZIzj0CAQYFK4EEACIDYgAEexNURoB9KE93MEtEAlJG +obz4LS/pD2hc8Gczix1WhVvpJ8bN5zCDXaKdnDMCebetyRQsmQ2LYlfmCwpZwSDu +0zowB11Pt3I5Avu2EEcuKTlKIDMBeZ1WWuOd3Tf7MEAMo0IwQDAPBgNVHRMBAf8E +BTADAQH/MB0GA1UdDgQWBBSaYbZPBvFLikSAjpa8mRJvyArMxzAOBgNVHQ8BAf8E +BAMCAYYwCgYIKoZIzj0EAwMDaQAwZgIxAOEJkuh3Zjb7Ih/zuNRd1RBqmIYcnyw0 +nwUZczKXry+9XebYj3VQxSRNadrarPWVqgIxAMg1dyGoDAYjY/L/9YElyMnvHltO +PwpJShmqHvCLc/mXMgjjYb/akK7yGthvW6j/uQ== +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIIGCDCCA/CgAwIBAgIQChu3v5W1Doil3v6pgRIcVzANBgkqhkiG9w0BAQwFADCB +nDELMAkGA1UEBhMCVVMxIjAgBgNVBAoMGUFtYXpvbiBXZWIgU2VydmljZXMsIElu +Yy4xEzARBgNVBAsMCkFtYXpvbiBSRFMxCzAJBgNVBAgMAldBMTUwMwYDVQQDDCxB +bWF6b24gUkRTIEJldGEgdXMtZWFzdC0xIFJvb3QgQ0EgUlNBNDA5NiBHMTEQMA4G +A1UEBwwHU2VhdHRsZTAgFw0yMTA1MTgyMTM0MTVaGA8yMTIxMDUxODIyMzQxNVow +gZwxCzAJBgNVBAYTAlVTMSIwIAYDVQQKDBlBbWF6b24gV2ViIFNlcnZpY2VzLCBJ +bmMuMRMwEQYDVQQLDApBbWF6b24gUkRTMQswCQYDVQQIDAJXQTE1MDMGA1UEAwws +QW1hem9uIFJEUyBCZXRhIHVzLWVhc3QtMSBSb290IENBIFJTQTQwOTYgRzExEDAO +BgNVBAcMB1NlYXR0bGUwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQC1 +FUGQ5tf3OwpDR6hGBxhUcrkwKZhaXP+1St1lSOQvjG8wXT3RkKzRGMvb7Ee0kzqI +mzKKe4ASIhtV3UUWdlNmP0EA3XKnif6N79MismTeGkDj75Yzp5A6tSvqByCgxIjK +JqpJrch3Dszoyn8+XhwDxMZtkUa5nQVdJgPzJ6ltsQ8E4SWLyLtTu0S63jJDkqYY +S7cQblk7y7fel+Vn+LS5dGTdRRhMvSzEnb6mkVBaVzRyVX90FNUED06e8q+gU8Ob +htvQlf9/kRzHwRAdls2YBhH40ZeyhpUC7vdtPwlmIyvW5CZ/QiG0yglixnL6xahL +pbmTuTSA/Oqz4UGQZv2WzHe1lD2gRHhtFX2poQZeNQX8wO9IcUhrH5XurW/G9Xwl +Sat9CMPERQn4KC3HSkat4ir2xaEUrjfg6c4XsGyh2Pk/LZ0gLKum0dyWYpWP4JmM +RQNjrInXPbMhzQObozCyFT7jYegS/3cppdyy+K1K7434wzQGLU1gYXDKFnXwkX8R +bRKgx2pHNbH5lUddjnNt75+e8m83ygSq/ZNBUz2Ur6W2s0pl6aBjwaDES4VfWYlI +jokcmrGvJNDfQWygb1k00eF2bzNeNCHwgWsuo3HSxVgc/WGsbcGrTlDKfz+g3ich +bXUeUidPhRiv5UQIVCLIHpHuin3bj9lQO/0t6p+tAQIDAQABo0IwQDAPBgNVHRMB +Af8EBTADAQH/MB0GA1UdDgQWBBSFmMBgm5IsRv3hLrvDPIhcPweXYTAOBgNVHQ8B +Af8EBAMCAYYwDQYJKoZIhvcNAQEMBQADggIBAAa2EuozymOsQDJlEi7TqnyA2OhT +GXPfYqCyMJVkfrqNgcnsNpCAiNEiZbb+8sIPXnT8Ay8hrwJYEObJ5b7MHXpLuyft +z0Pu1oFLKnQxKjNxrIsCvaB4CRRdYjm1q7EqGhMGv76se9stOxkOqO9it31w/LoU +ENDk7GLsSqsV1OzYLhaH8t+MaNP6rZTSNuPrHwbV3CtBFl2TAZ7iKgKOhdFz1Hh9 +Pez0lG+oKi4mHZ7ajov6PD0W7njn5KqzCAkJR6OYmlNVPjir+c/vUtEs0j+owsMl +g7KE5g4ZpTRShyh5BjCFRK2tv0tkqafzNtxrKC5XNpEkqqVTCnLcKG+OplIEadtr +C7UWf4HyhCiR+xIyxFyR05p3uY/QQU/5uza7GlK0J+U1sBUytx7BZ+Fo8KQfPPqV +CqDCaYUksoJcnJE/KeoksyqNQys7sDGJhkd0NeUGDrFLKHSLhIwAMbEWnqGxvhli +E7sP2E5rI/I9Y9zTbLIiI8pfeZlFF8DBdoP/Hzg8pqsiE/yiXSFTKByDwKzGwNqz +F0VoFdIZcIbLdDbzlQitgGpJtvEL7HseB0WH7B2PMMD8KPJlYvPveO3/6OLzCsav ++CAkvk47NQViKMsUTKOA0JDCW+u981YRozxa3K081snhSiSe83zIPBz1ikldXxO9 +6YYLNPRrj3mi9T/f +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIICrjCCAjSgAwIBAgIRAMkvdFnVDb0mWWFiXqnKH68wCgYIKoZIzj0EAwMwgZYx +CzAJBgNVBAYTAlVTMSIwIAYDVQQKDBlBbWF6b24gV2ViIFNlcnZpY2VzLCBJbmMu +MRMwEQYDVQQLDApBbWF6b24gUkRTMQswCQYDVQQIDAJXQTEvMC0GA1UEAwwmQW1h +em9uIFJEUyB1cy13ZXN0LTEgUm9vdCBDQSBFQ0MzODQgRzExEDAOBgNVBAcMB1Nl +YXR0bGUwIBcNMjEwNTE5MTkxMzI0WhgPMjEyMTA1MTkyMDEzMjRaMIGWMQswCQYD +VQQGEwJVUzEiMCAGA1UECgwZQW1hem9uIFdlYiBTZXJ2aWNlcywgSW5jLjETMBEG +A1UECwwKQW1hem9uIFJEUzELMAkGA1UECAwCV0ExLzAtBgNVBAMMJkFtYXpvbiBS +RFMgdXMtd2VzdC0xIFJvb3QgQ0EgRUNDMzg0IEcxMRAwDgYDVQQHDAdTZWF0dGxl +MHYwEAYHKoZIzj0CAQYFK4EEACIDYgAEy86DB+9th/0A5VcWqMSWDxIUblWTt/R0 +ao6Z2l3vf2YDF2wt1A2NIOGpfQ5+WAOJO/IQmnV9LhYo+kacB8sOnXdQa6biZZkR +IyouUfikVQAKWEJnh1Cuo5YMM4E2sUt5o0IwQDAPBgNVHRMBAf8EBTADAQH/MB0G +A1UdDgQWBBQ8u3OnecANmG8OoT7KLWDuFzZwBTAOBgNVHQ8BAf8EBAMCAYYwCgYI +KoZIzj0EAwMDaAAwZQIwQ817qkb7mWJFnieRAN+m9W3E0FLVKaV3zC5aYJUk2fcZ +TaUx3oLp3jPLGvY5+wgeAjEA6wAicAki4ZiDfxvAIuYiIe1OS/7H5RA++R8BH6qG +iRzUBM/FItFpnkus7u/eTkvo +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIICrzCCAjWgAwIBAgIQS/+Ryfgb/IOVEa1pWoe8oTAKBggqhkjOPQQDAzCBlzEL +MAkGA1UEBhMCVVMxIjAgBgNVBAoMGUFtYXpvbiBXZWIgU2VydmljZXMsIEluYy4x +EzARBgNVBAsMCkFtYXpvbiBSRFMxCzAJBgNVBAgMAldBMTAwLgYDVQQDDCdBbWF6 +b24gUkRTIGFwLXNvdXRoLTIgUm9vdCBDQSBFQ0MzODQgRzExEDAOBgNVBAcMB1Nl +YXR0bGUwIBcNMjIwNjA2MjE1NDQyWhgPMjEyMjA2MDYyMjU0NDJaMIGXMQswCQYD +VQQGEwJVUzEiMCAGA1UECgwZQW1hem9uIFdlYiBTZXJ2aWNlcywgSW5jLjETMBEG +A1UECwwKQW1hem9uIFJEUzELMAkGA1UECAwCV0ExMDAuBgNVBAMMJ0FtYXpvbiBS +RFMgYXAtc291dGgtMiBSb290IENBIEVDQzM4NCBHMTEQMA4GA1UEBwwHU2VhdHRs +ZTB2MBAGByqGSM49AgEGBSuBBAAiA2IABDsX6fhdUWBQpYTdseBD/P3s96Dtw2Iw +OrXKNToCnmX5nMkUGdRn9qKNiz1pw3EPzaPxShbYwQ7LYP09ENK/JN4QQjxMihxC +jLFxS85nhBQQQGRCWikDAe38mD8fSvREQKNCMEAwDwYDVR0TAQH/BAUwAwEB/zAd +BgNVHQ4EFgQUIh1xZiseQYFjPYKJmGbruAgRH+AwDgYDVR0PAQH/BAQDAgGGMAoG +CCqGSM49BAMDA2gAMGUCMFudS4zLy+UUGrtgNLtRMcu/DZ9BUzV4NdHxo0bkG44O +thnjl4+wTKI6VbyAbj2rkgIxAOHps8NMITU5DpyiMnKTxV8ubb/WGHrLl0BjB8Lw +ETVJk5DNuZvsIIcm7ykk6iL4Tw== +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIIGBDCCA+ygAwIBAgIQDcEmNIAVrDpUw5cH5ynutDANBgkqhkiG9w0BAQwFADCB +mjELMAkGA1UEBhMCVVMxIjAgBgNVBAoMGUFtYXpvbiBXZWIgU2VydmljZXMsIElu +Yy4xEzARBgNVBAsMCkFtYXpvbiBSRFMxCzAJBgNVBAgMAldBMTMwMQYDVQQDDCpB +bWF6b24gUkRTIG1lLWNlbnRyYWwtMSBSb290IENBIFJTQTQwOTYgRzExEDAOBgNV +BAcMB1NlYXR0bGUwIBcNMjIwNTA3MDA0MDIzWhgPMjEyMjA1MDcwMTQwMjNaMIGa +MQswCQYDVQQGEwJVUzEiMCAGA1UECgwZQW1hem9uIFdlYiBTZXJ2aWNlcywgSW5j +LjETMBEGA1UECwwKQW1hem9uIFJEUzELMAkGA1UECAwCV0ExMzAxBgNVBAMMKkFt +YXpvbiBSRFMgbWUtY2VudHJhbC0xIFJvb3QgQ0EgUlNBNDA5NiBHMTEQMA4GA1UE +BwwHU2VhdHRsZTCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBAKvADk8t +Fl9bFlU5sajLPPDSOUpPAkKs6iPlz+27o1GJC88THcOvf3x0nVAcu9WYe9Qaas+4 +j4a0vv51agqyODRD/SNi2HnqW7DbtLPAm6KBHe4twl28ItB/JD5g7u1oPAHFoXMS +cH1CZEAs5RtlZGzJhcBXLFsHNv/7+SCLyZ7+2XFh9OrtgU4wMzkHoRNndhfwV5bu +17bPTwuH+VxH37zXf1mQ/KjhuJos0C9dL0FpjYBAuyZTAWhZKs8dpSe4DI544z4w +gkwUB4bC2nA1TBzsywEAHyNuZ/xRjNpWvx0ToWAA2iFJqC3VO3iKcnBplMvaUuMt +jwzVSNBnKcoabXCZL2XDLt4YTZR8FSwz05IvsmwcPB7uNTBXq3T9sjejW8QQK3vT +tzyfLq4jKmQE7PoS6cqYm+hEPm2hDaC/WP9bp3FdEJxZlPH26fq1b7BWYWhQ9pBA +Nv9zTnzdR1xohTyOJBUFQ81ybEzabqXqVXUIANqIOaNcTB09/sLJ7+zuMhp3mwBu +LtjfJv8PLuT1r63bU3seROhKA98b5KfzjvbvPSg3vws78JQyoYGbqNyDfyjVjg3U +v//AdVuPie6PNtdrW3upZY4Qti5IjP9e3kimaJ+KAtTgMRG56W0WxD3SP7+YGGbG +KhntDOkKsN39hLpn9UOafTIqFu7kIaueEy/NAgMBAAGjQjBAMA8GA1UdEwEB/wQF +MAMBAf8wHQYDVR0OBBYEFHAems86dTwdZbLe8AaPy3kfIUVoMA4GA1UdDwEB/wQE +AwIBhjANBgkqhkiG9w0BAQwFAAOCAgEAOBHpp0ICx81kmeoBcZTrMdJs2gnhcd85 +FoSCjXx9H5XE5rmN/lQcxxOgj8hr3uPuLdLHu+i6THAyzjrl2NA1FWiqpfeECGmy +0jm7iZsYORgGQYp/VKnDrwnKNSqlZvOuRr0kfUexwFlr34Y4VmupvEOK/RdGsd3S ++3hiemcHse9ST/sJLHx962AWMkN86UHPscJEe4+eT3f2Wyzg6La8ARwdWZSNS+WH +ZfybrncMmuiXuUdHv9XspPsqhKgtHhcYeXOGUtrwQPLe3+VJZ0LVxhlTWr9951GZ +GfmWwTV/9VsyKVaCFIXeQ6L+gjcKyEzYF8wpMtQlSc7FFqwgC4bKxvMBSaRy88Nr +lV2+tJD/fr8zGUeBK44Emon0HKDBWGX+/Hq1ZIv0Da0S+j6LbA4fusWxtGfuGha+ +luhHgVInCpALIOamiBEdGhILkoTtx7JrYppt3/Raqg9gUNCOOYlCvGhqX7DXeEfL +DGabooiY2FNWot6h04JE9nqGj5QqT8D6t/TL1nzxhRPzbcSDIHUd/b5R+a0bAA+7 +YTU6JqzEVCWKEIEynYmqikgLMGB/OzWsgyEL6822QW6hJAQ78XpbNeCzrICF4+GC +7KShLnwuWoWpAb26268lvOEvCTFM47VC6jNQl97md+2SA9Ma81C9wflid2M83Wle +cuLMVcQZceE= +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIIEADCCAuigAwIBAgIQAhAteLRCvizAElaWORFU2zANBgkqhkiG9w0BAQsFADCB +mDELMAkGA1UEBhMCVVMxIjAgBgNVBAoMGUFtYXpvbiBXZWIgU2VydmljZXMsIElu +Yy4xEzARBgNVBAsMCkFtYXpvbiBSRFMxCzAJBgNVBAgMAldBMTEwLwYDVQQDDChB +bWF6b24gUkRTIG1lLXNvdXRoLTEgUm9vdCBDQSBSU0EyMDQ4IEcxMRAwDgYDVQQH +DAdTZWF0dGxlMCAXDTIxMDUyMDE3MDkxNloYDzIwNjEwNTIwMTgwOTE2WjCBmDEL +MAkGA1UEBhMCVVMxIjAgBgNVBAoMGUFtYXpvbiBXZWIgU2VydmljZXMsIEluYy4x +EzARBgNVBAsMCkFtYXpvbiBSRFMxCzAJBgNVBAgMAldBMTEwLwYDVQQDDChBbWF6 +b24gUkRTIG1lLXNvdXRoLTEgUm9vdCBDQSBSU0EyMDQ4IEcxMRAwDgYDVQQHDAdT +ZWF0dGxlMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA+qg7JAcOVKjh +N83SACnBFZPyB63EusfDr/0V9ZdL8lKcmZX9sv/CqoBo3N0EvBqHQqUUX6JvFb7F +XrMUZ740kr28gSRALfXTFgNODjXeDsCtEkKRTkac/UM8xXHn+hR7UFRPHS3e0GzI +iLiwQWDkr0Op74W8aM0CfaVKvh2bp4BI1jJbdDnQ9OKXpOxNHGUf0ZGb7TkNPkgI +b2CBAc8J5o3H9lfw4uiyvl6Fz5JoP+A+zPELAioYBXDrbE7wJeqQDJrETWqR9VEK +BXURCkVnHeaJy123MpAX2ozf4pqk0V0LOEOZRS29I+USF5DcWr7QIXR/w2I8ws1Q +7ys+qbE+kQIDAQABo0IwQDAPBgNVHRMBAf8EBTADAQH/MB0GA1UdDgQWBBQFJ16n +1EcCMOIhoZs/F9sR+Jy++zAOBgNVHQ8BAf8EBAMCAYYwDQYJKoZIhvcNAQELBQAD +ggEBAOc5nXbT3XTDEZsxX2iD15YrQvmL5m13B3ImZWpx/pqmObsgx3/dg75rF2nQ +qS+Vl+f/HLh516pj2BPP/yWCq12TRYigGav8UH0qdT3CAClYy2o+zAzUJHm84oiB +ud+6pFVGkbqpsY+QMpJUbZWu52KViBpJMYsUEy+9cnPSFRVuRAHjYynSiLk2ZEjb +Wkdc4x0nOZR5tP0FgrX0Ve2KcjFwVQJVZLgOUqmFYQ/G0TIIGTNh9tcmR7yp+xJR +A2tbPV2Z6m9Yxx4E8lLEPNuoeouJ/GR4CkMEmF8cLwM310t174o3lKKUXJ4Vs2HO +Wj2uN6R9oI+jGLMSswTzCNV1vgc= +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIICtjCCAj2gAwIBAgIQM+ObZzo0HZj7HpGdeMmx/zAKBggqhkjOPQQDAzCBmzEL +MAkGA1UEBhMCVVMxIjAgBgNVBAoMGUFtYXpvbiBXZWIgU2VydmljZXMsIEluYy4x +EzARBgNVBAsMCkFtYXpvbiBSRFMxCzAJBgNVBAgMAldBMTQwMgYDVQQDDCtBbWF6 +b24gUkRTIGFwLXNvdXRoZWFzdC01IFJvb3QgQ0EgRUNDMzg0IEcxMRAwDgYDVQQH +DAdTZWF0dGxlMCAXDTI0MDUxNTIxNTA0NloYDzIxMjQwNTE1MjI1MDQ2WjCBmzEL +MAkGA1UEBhMCVVMxIjAgBgNVBAoMGUFtYXpvbiBXZWIgU2VydmljZXMsIEluYy4x +EzARBgNVBAsMCkFtYXpvbiBSRFMxCzAJBgNVBAgMAldBMTQwMgYDVQQDDCtBbWF6 +b24gUkRTIGFwLXNvdXRoZWFzdC01IFJvb3QgQ0EgRUNDMzg0IEcxMRAwDgYDVQQH +DAdTZWF0dGxlMHYwEAYHKoZIzj0CAQYFK4EEACIDYgAEhSrJY/MXuQyTqK1dnLK6 +uWUx/KxsGCMCBXKthi0spP90CjfOYYxDcGD7zgUtk+LCEK2vneuewAPhlUgqXzaZ +PYDzk2WUznIPiIBvVo32U4vUnV/vSWqzhSKevsOakiPso0IwQDAPBgNVHRMBAf8E +BTADAQH/MB0GA1UdDgQWBBRw/PJZ4fwnZo25vVSB80KtyKWqmTAOBgNVHQ8BAf8E +BAMCAYYwCgYIKoZIzj0EAwMDZwAwZAIwLNcaZNOvCLTumHlJydm+9lB6bcxnaLmb +esoToveXQABKl84kGNI1gaDKOvvLsPbWAjBIqfDMb83RXw7q2C501W5hzsbZ1ZQs +8+tffIuCrMMGWDLqoUksWJHiocLOfe9gwm4= +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIICuDCCAj6gAwIBAgIRAOocLeZWjYkG/EbHmscuy8gwCgYIKoZIzj0EAwMwgZsx +CzAJBgNVBAYTAlVTMSIwIAYDVQQKDBlBbWF6b24gV2ViIFNlcnZpY2VzLCBJbmMu +MRMwEQYDVQQLDApBbWF6b24gUkRTMQswCQYDVQQIDAJXQTE0MDIGA1UEAwwrQW1h +em9uIFJEUyBhcC1zb3V0aGVhc3QtMSBSb290IENBIEVDQzM4NCBHMTEQMA4GA1UE +BwwHU2VhdHRsZTAgFw0yMTA1MjEyMTUwMDFaGA8yMTIxMDUyMTIyNTAwMVowgZsx +CzAJBgNVBAYTAlVTMSIwIAYDVQQKDBlBbWF6b24gV2ViIFNlcnZpY2VzLCBJbmMu +MRMwEQYDVQQLDApBbWF6b24gUkRTMQswCQYDVQQIDAJXQTE0MDIGA1UEAwwrQW1h +em9uIFJEUyBhcC1zb3V0aGVhc3QtMSBSb290IENBIEVDQzM4NCBHMTEQMA4GA1UE +BwwHU2VhdHRsZTB2MBAGByqGSM49AgEGBSuBBAAiA2IABCEr3jq1KtRncnZfK5cq +btY0nW6ZG3FMbh7XwBIR6Ca0f8llGZ4vJEC1pXgiM/4Dh045B9ZIzNrR54rYOIfa +2NcYZ7mk06DjIQML64hbAxbQzOAuNzLPx268MrlL2uW2XaNCMEAwDwYDVR0TAQH/ +BAUwAwEB/zAdBgNVHQ4EFgQUln75pChychwN4RfHl+tOinMrfVowDgYDVR0PAQH/ +BAQDAgGGMAoGCCqGSM49BAMDA2gAMGUCMGiyPINRU1mwZ4Crw01vpuPvxZxb2IOr +yX3RNlOIu4We1H+5dQk5tIvH8KGYFbWEpAIxAO9NZ6/j9osMhLgZ0yj0WVjb+uZx +YlZR9fyFisY/jNfX7QhSk+nrc3SFLRUNtpXrng== +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIIEBTCCAu2gAwIBAgIRAKiaRZatN8eiz9p0s0lu0rQwDQYJKoZIhvcNAQELBQAw +gZoxCzAJBgNVBAYTAlVTMSIwIAYDVQQKDBlBbWF6b24gV2ViIFNlcnZpY2VzLCBJ +bmMuMRMwEQYDVQQLDApBbWF6b24gUkRTMQswCQYDVQQIDAJXQTEzMDEGA1UEAwwq +QW1hem9uIFJEUyBjYS1jZW50cmFsLTEgUm9vdCBDQSBSU0EyMDQ4IEcxMRAwDgYD +VQQHDAdTZWF0dGxlMCAXDTIxMDUyMTIyMDIzNVoYDzIwNjEwNTIxMjMwMjM1WjCB +mjELMAkGA1UEBhMCVVMxIjAgBgNVBAoMGUFtYXpvbiBXZWIgU2VydmljZXMsIElu +Yy4xEzARBgNVBAsMCkFtYXpvbiBSRFMxCzAJBgNVBAgMAldBMTMwMQYDVQQDDCpB +bWF6b24gUkRTIGNhLWNlbnRyYWwtMSBSb290IENBIFJTQTIwNDggRzExEDAOBgNV +BAcMB1NlYXR0bGUwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCygVMf +qB865IR9qYRBRFHn4eAqGJOCFx+UbraQZmjr/mnRqSkY+nhbM7Pn/DWOrRnxoh+w +q5F9ZxdZ5D5T1v6kljVwxyfFgHItyyyIL0YS7e2h7cRRscCM+75kMedAP7icb4YN +LfWBqfKHbHIOqvvQK8T6+Emu/QlG2B5LvuErrop9K0KinhITekpVIO4HCN61cuOe +CADBKF/5uUJHwS9pWw3uUbpGUwsLBuhJzCY/OpJlDqC8Y9aToi2Ivl5u3/Q/sKjr +6AZb9lx4q3J2z7tJDrm5MHYwV74elGSXoeoG8nODUqjgklIWAPrt6lQ3WJpO2kug +8RhCdSbWkcXHfX95AgMBAAGjQjBAMA8GA1UdEwEB/wQFMAMBAf8wHQYDVR0OBBYE +FOIxhqTPkKVqKBZvMWtKewKWDvDBMA4GA1UdDwEB/wQEAwIBhjANBgkqhkiG9w0B +AQsFAAOCAQEAqoItII89lOl4TKvg0I1EinxafZLXIheLcdGCxpjRxlZ9QMQUN3yb +y/8uFKBL0otbQgJEoGhxm4h0tp54g28M6TN1U0332dwkjYxUNwvzrMaV5Na55I2Z +1hq4GB3NMXW+PvdtsgVOZbEN+zOyOZ5MvJHEQVkT3YRnf6avsdntltcRzHJ16pJc +Y8rR7yWwPXh1lPaPkxddrCtwayyGxNbNmRybjR48uHRhwu7v2WuAMdChL8H8bp89 +TQLMrMHgSbZfee9hKhO4Zebelf1/cslRSrhkG0ESq6G5MUINj6lMg2g6F0F7Xz2v +ncD/vuRN5P+vT8th/oZ0Q2Gc68Pun0cn/g== +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIID/zCCAuegAwIBAgIRAJYlnmkGRj4ju/2jBQsnXJYwDQYJKoZIhvcNAQELBQAw +gZcxCzAJBgNVBAYTAlVTMSIwIAYDVQQKDBlBbWF6b24gV2ViIFNlcnZpY2VzLCBJ +bmMuMRMwEQYDVQQLDApBbWF6b24gUkRTMQswCQYDVQQIDAJXQTEwMC4GA1UEAwwn +QW1hem9uIFJEUyB1cy1lYXN0LTIgUm9vdCBDQSBSU0EyMDQ4IEcxMRAwDgYDVQQH +DAdTZWF0dGxlMCAXDTIxMDUyMTIzMDQ0NFoYDzIwNjEwNTIyMDAwNDQ0WjCBlzEL +MAkGA1UEBhMCVVMxIjAgBgNVBAoMGUFtYXpvbiBXZWIgU2VydmljZXMsIEluYy4x +EzARBgNVBAsMCkFtYXpvbiBSRFMxCzAJBgNVBAgMAldBMTAwLgYDVQQDDCdBbWF6 +b24gUkRTIHVzLWVhc3QtMiBSb290IENBIFJTQTIwNDggRzExEDAOBgNVBAcMB1Nl +YXR0bGUwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQC74V3eigv+pCj5 +nqDBqplY0Jp16pTeNB06IKbzb4MOTvNde6QjsZxrE1xUmprT8LxQqN9tI3aDYEYk +b9v4F99WtQVgCv3Y34tYKX9NwWQgwS1vQwnIR8zOFBYqsAsHEkeJuSqAB12AYUSd +Zv2RVFjiFmYJho2X30IrSLQfS/IE3KV7fCyMMm154+/K1Z2IJlcissydEAwgsUHw +edrE6CxJVkkJ3EvIgG4ugK/suxd8eEMztaQYJwSdN8TdfT59LFuSPl7zmF3fIBdJ +//WexcQmGabaJ7Xnx+6o2HTfkP8Zzzzaq8fvjAcvA7gyFH5EP26G2ZqMG+0y4pTx +SPVTrQEXAgMBAAGjQjBAMA8GA1UdEwEB/wQFMAMBAf8wHQYDVR0OBBYEFIWWuNEF +sUMOC82XlfJeqazzrkPDMA4GA1UdDwEB/wQEAwIBhjANBgkqhkiG9w0BAQsFAAOC +AQEAgClmxcJaQTGpEZmjElL8G2Zc8lGc+ylGjiNlSIw8X25/bcLRptbDA90nuP+q +zXAMhEf0ccbdpwxG/P5a8JipmHgqQLHfpkvaXx+0CuP++3k+chAJ3Gk5XtY587jX ++MJfrPgjFt7vmMaKmynndf+NaIJAYczjhJj6xjPWmGrjM3MlTa9XesmelMwP3jep +bApIWAvCYVjGndbK9byyMq1nyj0TUzB8oJZQooaR3MMjHTmADuVBylWzkRMxbKPl +4Nlsk4Ef1JvIWBCzsMt+X17nuKfEatRfp3c9tbpGlAE/DSP0W2/Lnayxr4RpE9ds +ICF35uSis/7ZlsftODUe8wtpkQ== +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIIECDCCAvCgAwIBAgIQTcWg4evi5wHEIHLNrHCqQjANBgkqhkiG9w0BAQsFADCB +nDELMAkGA1UEBhMCVVMxIjAgBgNVBAoMGUFtYXpvbiBXZWIgU2VydmljZXMsIElu +Yy4xEzARBgNVBAsMCkFtYXpvbiBSRFMxCzAJBgNVBAgMAldBMTUwMwYDVQQDDCxB +bWF6b24gUkRTIGFwLXNvdXRoZWFzdC03IFJvb3QgQ0EgUlNBMjA0OCBHMTEQMA4G +A1UEBwwHU2VhdHRsZTAgFw0yNDA5MTIxNTU0MzFaGA8yMDY0MDkxMjE2NTQzMVow +gZwxCzAJBgNVBAYTAlVTMSIwIAYDVQQKDBlBbWF6b24gV2ViIFNlcnZpY2VzLCBJ +bmMuMRMwEQYDVQQLDApBbWF6b24gUkRTMQswCQYDVQQIDAJXQTE1MDMGA1UEAwws +QW1hem9uIFJEUyBhcC1zb3V0aGVhc3QtNyBSb290IENBIFJTQTIwNDggRzExEDAO +BgNVBAcMB1NlYXR0bGUwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCS +X21nYtSipOhpdF6QfLE4BTwbtrXCR3EBhOHB1MfwwjNh25uex3X/gHW/sUTgC/oe +Oboi+3I/HN6a5MneTcVVps8AL8rJGym0ShSIYza/3MyT+PtfqDNmYfmF8VRIhNSR +CoYW93F/BoZF7bFk4ljOrBSrRbfb1qmEtkTPNGBRnJ0jh05Fwq5a5XnkGcOGNDyH +kGt9ZaUm+cJmzAa1omHspCjgg6854CNs4k5ovT2vaZ/0yAogdSpkB9INLM0ZMqJ+ +QoKAyN6hdISjwEEZrl+0OKymc2jR+NpQcd3378bDIfg3iYvuvTp84kKpZ4gRaukm +mSUg/PQqmGdBlAivOiknAgMBAAGjQjBAMA8GA1UdEwEB/wQFMAMBAf8wHQYDVR0O +BBYEFDsAilCg2DzFiZheGlKVb74AubP2MA4GA1UdDwEB/wQEAwIBhjANBgkqhkiG +9w0BAQsFAAOCAQEACUGKd2GVv87xnVnPajqbi7GkMd+XGwyG1P8Nkon9rfUgwgHR +dHDsO6jIKf3ZEzNcMgMyV5sXs2944WRhGkYxQ62wbkEaqtjNExmlmUiS3vvrGOfg +BmRhJvAOM5HbNMqmi5BC4GlhHWQKhRbStwhvbVYnARBVfR/Wz4Qb4fizaXbuNWAo +V0XBQc+67lto2eBQ84KQXTp60FdwSBzltbM7sZmp6dMNgnfPNUrdxurVboF+VsxS +3aUJnmEi6TJAGL2SXLermB6HiTgxtxOJUiefu4Ipv0JEg41OzbKWsJXTaSH0QDb1 +nSeMyoXqF3ixcAhQbw/7NrzMMlf3NmMaVlohVA== +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIICrjCCAjOgAwIBAgIQS7vMpOTVq2Jw457NdZ2ffjAKBggqhkjOPQQDAzCBljEL +MAkGA1UEBhMCVVMxIjAgBgNVBAoMGUFtYXpvbiBXZWIgU2VydmljZXMsIEluYy4x +EzARBgNVBAsMCkFtYXpvbiBSRFMxCzAJBgNVBAgMAldBMS8wLQYDVQQDDCZBbWF6 +b24gUkRTIGNhLXdlc3QtMSBSb290IENBIEVDQzM4NCBHMTEQMA4GA1UEBwwHU2Vh +dHRsZTAgFw0yMzA5MTkyMjExNDNaGA8yMTIzMDkxOTIzMTE0M1owgZYxCzAJBgNV +BAYTAlVTMSIwIAYDVQQKDBlBbWF6b24gV2ViIFNlcnZpY2VzLCBJbmMuMRMwEQYD +VQQLDApBbWF6b24gUkRTMQswCQYDVQQIDAJXQTEvMC0GA1UEAwwmQW1hem9uIFJE +UyBjYS13ZXN0LTEgUm9vdCBDQSBFQ0MzODQgRzExEDAOBgNVBAcMB1NlYXR0bGUw +djAQBgcqhkjOPQIBBgUrgQQAIgNiAARdgGSs/F2lpWKqS1ZpcmatFED1JurmNbXG +Sqhv1A/geHrKCS15MPwjtnfZiujYKY4fNkCCUseoGDwkC4281nwkokvnfWR1/cXy +LxfACoXNxsI4b+37CezSUBl48/5p1/OjQjBAMA8GA1UdEwEB/wQFMAMBAf8wHQYD +VR0OBBYEFFhLokGBuJGwKJhZcYSYKyZIitJtMA4GA1UdDwEB/wQEAwIBhjAKBggq +hkjOPQQDAwNpADBmAjEA8aQQlzJRHbqFsRY4O3u/cN0T8dzjcqnYn4NV1w+jvhzt +QPJLB+ggGyQhoFR6G2UrAjEA0be8OP5MWXD8d01KKbo5Dpy6TwukF5qoJmkFJKS3 +bKfEMvFWxXoV06HNZFWdI80u +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +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 +Pqj2 +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +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 +KDPtFEk= +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIICtzCCAj2gAwIBAgIQVZ5Y/KqjR4XLou8MCD5pOjAKBggqhkjOPQQDAzCBmzEL +MAkGA1UEBhMCVVMxIjAgBgNVBAoMGUFtYXpvbiBXZWIgU2VydmljZXMsIEluYy4x +EzARBgNVBAsMCkFtYXpvbiBSRFMxCzAJBgNVBAgMAldBMTQwMgYDVQQDDCtBbWF6 +b24gUkRTIGFwLXNvdXRoZWFzdC00IFJvb3QgQ0EgRUNDMzg0IEcxMRAwDgYDVQQH +DAdTZWF0dGxlMCAXDTIyMDUyNTE2NTgzM1oYDzIxMjIwNTI1MTc1ODMzWjCBmzEL +MAkGA1UEBhMCVVMxIjAgBgNVBAoMGUFtYXpvbiBXZWIgU2VydmljZXMsIEluYy4x +EzARBgNVBAsMCkFtYXpvbiBSRFMxCzAJBgNVBAgMAldBMTQwMgYDVQQDDCtBbWF6 +b24gUkRTIGFwLXNvdXRoZWFzdC00IFJvb3QgQ0EgRUNDMzg0IEcxMRAwDgYDVQQH +DAdTZWF0dGxlMHYwEAYHKoZIzj0CAQYFK4EEACIDYgAEbo473OmpD5vkckdJajXg +brhmNFyoSa0WCY1njuZC2zMFp3zP6rX4I1r3imrYnJd9pFH/aSiV/r6L5ACE5RPx +4qdg5SQ7JJUaZc3DWsTOiOed7BCZSzM+KTYK/2QzDMApo0IwQDAPBgNVHRMBAf8E +BTADAQH/MB0GA1UdDgQWBBTmogc06+1knsej1ltKUOdWFvwgsjAOBgNVHQ8BAf8E +BAMCAYYwCgYIKoZIzj0EAwMDaAAwZQIxAIs7TlLMbGTWNXpGiKf9DxaM07d/iDHe +F/Vv/wyWSTGdobxBL6iArQNVXz0Gr4dvPAIwd0rsoa6R0x5mtvhdRPtM37FYrbHJ +pbV+OMusQqcSLseunLBoCHenvJW0QOCQ8EDY +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIIGBTCCA+2gAwIBAgIRAO9dVdiLTEGO8kjUFExJmgowDQYJKoZIhvcNAQEMBQAw +gZoxCzAJBgNVBAYTAlVTMSIwIAYDVQQKDBlBbWF6b24gV2ViIFNlcnZpY2VzLCBJ +bmMuMRMwEQYDVQQLDApBbWF6b24gUkRTMQswCQYDVQQIDAJXQTEzMDEGA1UEAwwq +QW1hem9uIFJEUyBpbC1jZW50cmFsLTEgUm9vdCBDQSBSU0E0MDk2IEcxMRAwDgYD +VQQHDAdTZWF0dGxlMCAXDTIyMTIwMjIwMjYwOFoYDzIxMjIxMjAyMjEyNjA4WjCB +mjELMAkGA1UEBhMCVVMxIjAgBgNVBAoMGUFtYXpvbiBXZWIgU2VydmljZXMsIElu +Yy4xEzARBgNVBAsMCkFtYXpvbiBSRFMxCzAJBgNVBAgMAldBMTMwMQYDVQQDDCpB +bWF6b24gUkRTIGlsLWNlbnRyYWwtMSBSb290IENBIFJTQTQwOTYgRzExEDAOBgNV +BAcMB1NlYXR0bGUwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQDkVHmJ +bUc8CNDGBcgPmXHSHj5dS1PDnnpk3doCu6pahyYXW8tqAOmOqsDuNz48exY7YVy4 +u9I9OPBeTYB9ZUKwxq+1ZNLsr1cwVz5DdOyDREVFOjlU4rvw0eTgzhP5yw/d+Ai/ ++WmPebZG0irwPKN2f60W/KJ45UNtR+30MT8ugfnPuSHWjjV+dqCOCp/mj8nOCckn +k8GoREwjuTFJMKInpQUC0BaVVX6LiIdgtoLY4wdx00EqNBuROoRTAvrked0jvm7J +UI39CSYxhNZJ9F6LdESZXjI4u2apfNQeSoy6WptxFHr+kh2yss1B2KT6lbwGjwWm +l9HODk9kbBNSy2NeewAms36q+p8wSLPavL28IRfK0UaBAiN1hr2a/2RDGCwOJmw6 +5erRC5IIX5kCStyXPEGhVPp18EvMuBd37eLIxjZBBO8AIDf4Ue8QmxSeZH0cT204 +3/Bd6XR6+Up9iMTxkHr1URcL1AR8Zd62lg/lbEfxePNMK9mQGxKP8eTMG5AjtW9G +TatEoRclgE0wZQalXHmKpBNshyYdGqQZhzL1MxCxWzfHNgZkTKIsdzxrjnP7RiBR +jdRH0YhXn6Y906QfLwMCaufwfQ5J8+nj/tu7nG138kSxsu6VUkhnQJhUcUsxuHD/ +NnBx0KGVEldtZiZf7ccgtRVp1lA0OrVtq3ZLMQIDAQABo0IwQDAPBgNVHRMBAf8E +BTADAQH/MB0GA1UdDgQWBBQ2WC3p8rWeE2N0S4Om01KsNLpk/jAOBgNVHQ8BAf8E +BAMCAYYwDQYJKoZIhvcNAQEMBQADggIBAFFEVDt45Obr6Ax9E4RMgsKjj4QjMFB9 +wHev1jL7hezl/ULrHuWxjIusaIZEIcKfn+v2aWtqOq13P3ht7jV5KsV29CmFuCdQ +q3PWiAXVs+hnMskTOmGMDnptqd6/UuSIha8mlOKKAvnmRQJvfX9hIfb/b/mVyKWD +uvTTmcy3cOTJY5ZIWGyzuvmcqA0YNcb7rkJt/iaLq4RX3/ofq4y4w36hefbcvj++ +pXHOmXk3dAej3y6SMBOUcGMyCJcCluRPNYKDTLn+fitcPxPC3JG7fI5bxQ0D6Hpa +qbyGBQu96sfahQyMc+//H8EYlo4b0vPeS5RFFXJS/VBf0AyNT4vVc7H17Q6KjeNp +wEARqsIa7UalHx9MnxrQ/LSTTxiC8qmDkIFuQtw8iQMN0SoL5S0eCZNRD31awgaY +y1PvY8JMN549ugIUjOXnown/OxharLW1evWUraU5rArq3JfeFpPXl4K/u10T5SCL +iJRoxFilGPMFE3hvnmbi5rEy8wRUn7TpLb4I4s/CB/lT2qZTPqvQHwxKCnMm9BKF +NHb4rLL5dCvUi5NJ6fQ/exOoGdOVSfT7jqFeq2TtNunERSz9vpriweliB6iIe1Al +Thj8aEs1GqA764rLVGA+vUe18NhjJm9EemrdIzjSQFy/NdbN/DMaHqEzJogWloAI +izQWYnCS19TJ +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIICvTCCAkOgAwIBAgIQCIY7E/bFvFN2lK9Kckb0dTAKBggqhkjOPQQDAzCBnjEL +MAkGA1UEBhMCVVMxIjAgBgNVBAoMGUFtYXpvbiBXZWIgU2VydmljZXMsIEluYy4x +EzARBgNVBAsMCkFtYXpvbiBSRFMxCzAJBgNVBAgMAldBMTcwNQYDVQQDDC5BbWF6 +b24gUkRTIFByZXZpZXcgdXMtZWFzdC0yIFJvb3QgQ0EgRUNDMzg0IEcxMRAwDgYD +VQQHDAdTZWF0dGxlMCAXDTIxMDUxODIxMDUxMFoYDzIxMjEwNTE4MjIwNTEwWjCB +njELMAkGA1UEBhMCVVMxIjAgBgNVBAoMGUFtYXpvbiBXZWIgU2VydmljZXMsIElu +Yy4xEzARBgNVBAsMCkFtYXpvbiBSRFMxCzAJBgNVBAgMAldBMTcwNQYDVQQDDC5B +bWF6b24gUkRTIFByZXZpZXcgdXMtZWFzdC0yIFJvb3QgQ0EgRUNDMzg0IEcxMRAw +DgYDVQQHDAdTZWF0dGxlMHYwEAYHKoZIzj0CAQYFK4EEACIDYgAEMI0hzf1JCEOI +Eue4+DmcNnSs2i2UaJxHMrNGGfU7b42a7vwP53F7045ffHPBGP4jb9q02/bStZzd +VHqfcgqkSRI7beBKjD2mfz82hF/wJSITTgCLs+NRpS6zKMFOFHUNo0IwQDAPBgNV +HRMBAf8EBTADAQH/MB0GA1UdDgQWBBS8uF/6hk5mPLH4qaWv9NVZaMmyTjAOBgNV +HQ8BAf8EBAMCAYYwCgYIKoZIzj0EAwMDaAAwZQIxAO7Pu9wzLyM0X7Q08uLIL+vL +qaxe3UFuzFTWjM16MLJHbzLf1i9IDFKz+Q4hXCSiJwIwClMBsqT49BPUxVsJnjGr +EbyEk6aOOVfY1p2yQL649zh3M4h8okLnwf+bYIb1YpeU +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIIEADCCAuigAwIBAgIQY+JhwFEQTe36qyRlUlF8ozANBgkqhkiG9w0BAQsFADCB +mDELMAkGA1UEBhMCVVMxIjAgBgNVBAoMGUFtYXpvbiBXZWIgU2VydmljZXMsIElu +Yy4xEzARBgNVBAsMCkFtYXpvbiBSRFMxCzAJBgNVBAgMAldBMTEwLwYDVQQDDChB +bWF6b24gUkRTIGFmLXNvdXRoLTEgUm9vdCBDQSBSU0EyMDQ4IEcxMRAwDgYDVQQH +DAdTZWF0dGxlMCAXDTIxMDUxOTE5MjQxNloYDzIwNjEwNTE5MjAyNDE2WjCBmDEL +MAkGA1UEBhMCVVMxIjAgBgNVBAoMGUFtYXpvbiBXZWIgU2VydmljZXMsIEluYy4x +EzARBgNVBAsMCkFtYXpvbiBSRFMxCzAJBgNVBAgMAldBMTEwLwYDVQQDDChBbWF6 +b24gUkRTIGFmLXNvdXRoLTEgUm9vdCBDQSBSU0EyMDQ4IEcxMRAwDgYDVQQHDAdT +ZWF0dGxlMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAnIye77j6ev40 +8wRPyN2OdKFSUfI9jB20Or2RLO+RDoL43+USXdrze0Wv4HMRLqaen9BcmCfaKMp0 +E4SFo47bXK/O17r6G8eyq1sqnHE+v288mWtYH9lAlSamNFRF6YwA7zncmE/iKL8J +0vePHMHP/B6svw8LULZCk+nZk3tgxQn2+r0B4FOz+RmpkoVddfqqUPMbKUxhM2wf +fO7F6bJaUXDNMBPhCn/3ayKCjYr49ErmnpYV2ZVs1i34S+LFq39J7kyv6zAgbHv9 ++/MtRMoRB1CjpqW0jIOZkHBdYcd1o9p1zFn591Do1wPkmMsWdjIYj+6e7UXcHvOB +2+ScIRAcnwIDAQABo0IwQDAPBgNVHRMBAf8EBTADAQH/MB0GA1UdDgQWBBQGtq2W +YSyMMxpdQ3IZvcGE+nyZqTAOBgNVHQ8BAf8EBAMCAYYwDQYJKoZIhvcNAQELBQAD +ggEBAEgoP3ixJsKSD5FN8dQ01RNHERl/IFbA7TRXfwC+L1yFocKnQh4Mp/msPRSV ++OeHIvemPW/wtZDJzLTOFJ6eTolGekHK1GRTQ6ZqsWiU2fmiOP8ks4oSpI+tQ9Lw +VrfZqTiEcS5wEIqyfUAZZfKDo7W1xp+dQWzfczSBuZJZwI5iaha7+ILM0r8Ckden +TVTapc5pLSoO15v0ziRuQ2bT3V3nwu/U0MRK44z+VWOJdSiKxdnOYDs8hFNnKhfe +klbTZF7kW7WbiNYB43OaAQBJ6BALZsIskEaqfeZT8FD71uN928TcEQyBDXdZpRN+ +iGQZDGhht0r0URGMDSs9waJtTfA= +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIICtDCCAjqgAwIBAgIRANtElQUxBpw5GPpq+Tqajs4wCgYIKoZIzj0EAwMwgZkx +CzAJBgNVBAYTAlVTMSIwIAYDVQQKDBlBbWF6b24gV2ViIFNlcnZpY2VzLCBJbmMu +MRMwEQYDVQQLDApBbWF6b24gUkRTMQswCQYDVQQIDAJXQTEyMDAGA1UEAwwpQW1h +em9uIFJEUyBteC1jZW50cmFsLTEgUm9vdCBDQSBFQ0MzODQgRzExEDAOBgNVBAcM +B1NlYXR0bGUwIBcNMjQwOTMwMTYxNDAwWhgPMjEyNDA5MzAxNzEzNTlaMIGZMQsw +CQYDVQQGEwJVUzEiMCAGA1UECgwZQW1hem9uIFdlYiBTZXJ2aWNlcywgSW5jLjET +MBEGA1UECwwKQW1hem9uIFJEUzELMAkGA1UECAwCV0ExMjAwBgNVBAMMKUFtYXpv +biBSRFMgbXgtY2VudHJhbC0xIFJvb3QgQ0EgRUNDMzg0IEcxMRAwDgYDVQQHDAdT +ZWF0dGxlMHYwEAYHKoZIzj0CAQYFK4EEACIDYgAEB5xlumRhIemjIWjxWQHqDYG7 +I24vWwUWZjcxcqpDHv6ThnJRsN4TCbWiSfjCOTeW9ZMQiMm6xlxug1nXkDGAyuHL +ze0PqtP0rUPi25wmp3F6L4vq96az41xtGa4wc/xSo0IwQDAPBgNVHRMBAf8EBTAD +AQH/MB0GA1UdDgQWBBSEfpD78dOGnSCKk9cYr2mAc4j9cTAOBgNVHQ8BAf8EBAMC +AYYwCgYIKoZIzj0EAwMDaAAwZQIxANNu+hmW2xOajUvFzdVV4fO0Slj6BRklQfjO +hSF/1NvfZK9pF/QDIyknmK5KS+bY+QIwZCxY71nDC4n4Jw1l0gF+krPg3gNS2KKF +FdrlaY1qdiyvyG6t6tp84q9XmGJ1rOhJ +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIIECDCCAvCgAwIBAgIQEbIZbn8kcnd/sTnZkdoDkzANBgkqhkiG9w0BAQsFADCB +nDELMAkGA1UEBhMCVVMxIjAgBgNVBAoMGUFtYXpvbiBXZWIgU2VydmljZXMsIElu +Yy4xEzARBgNVBAsMCkFtYXpvbiBSRFMxCzAJBgNVBAgMAldBMTUwMwYDVQQDDCxB +bWF6b24gUkRTIGFwLXNvdXRoZWFzdC01IFJvb3QgQ0EgUlNBMjA0OCBHMTEQMA4G +A1UEBwwHU2VhdHRsZTAgFw0yNDA1MTUyMTUwMzdaGA8yMDY0MDUxNTIyNTAzN1ow +gZwxCzAJBgNVBAYTAlVTMSIwIAYDVQQKDBlBbWF6b24gV2ViIFNlcnZpY2VzLCBJ +bmMuMRMwEQYDVQQLDApBbWF6b24gUkRTMQswCQYDVQQIDAJXQTE1MDMGA1UEAwws +QW1hem9uIFJEUyBhcC1zb3V0aGVhc3QtNSBSb290IENBIFJTQTIwNDggRzExEDAO +BgNVBAcMB1NlYXR0bGUwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCK +c15oRFUw/MiZQ/qkfOlrcc/PC9TdxGjUqdZyQGqBWrFauIbsK7U0qTeTibt7t7cL +hBWmqb3eefU8e+JZwJ20/cFfWINEjp9xLKV5pzfcRH+BJF3Sa4iLeLSi8CEp5qvf +k70ADs2kye17q29G01NfCG9T2oMEEJQof1nKcfwjayjx7uyBPHtR0a2SC88QlSl9 +9a009S0pUoISV3Zu/U+B6vUlBnGuIt+EsEFH0r19w/VRSO5mg9ylxh0/X5HXeBK5 +UxpNpXI9rPNd/AMTrv7FTyWsqkeSRS2lyT/8wyatApcCdPLyJDx7wZLY8/wARz7p +zi/uEKlhrrSDhDq2I7JFAgMBAAGjQjBAMA8GA1UdEwEB/wQFMAMBAf8wHQYDVR0O +BBYEFAHKs1jyaNzThRo5XHN/dNJDtVNHMA4GA1UdDwEB/wQEAwIBhjANBgkqhkiG +9w0BAQsFAAOCAQEAVAdR60a4czTpyu3JHj6oNVQUTt1D1jD/y1fZcc5a77fa2Qc6 +ZZEVBadpXAwkUQDbVRu/h6OrPhWKbQNLlTS1xzGuGeVbXSczvj37UB11WQfFN3M9 +Dpe5LTL0MCPO+elHzXrBhjhi9euCHXHDdvv4AZl7tfWuOrBdeBThXIehKniJmAjt +vq2mIHHThw2Wr+E65WerOVU+jepsG//1EkgrKfcGoS646jQXXKabW3cn0ymEV1/M +DhFbV05Jfvu969qcA3+TH1FaN/lAbwuSLFZ4HLFFjq7RVl/X//lyXl1q/coUdQXC +awfL88gOd/cYz0n5xm/S+gJUtOcz/dR6kV36NQ== +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIIF/jCCA+agAwIBAgIQXY/dmS+72lZPranO2JM9jjANBgkqhkiG9w0BAQwFADCB +lzELMAkGA1UEBhMCVVMxIjAgBgNVBAoMGUFtYXpvbiBXZWIgU2VydmljZXMsIElu +Yy4xEzARBgNVBAsMCkFtYXpvbiBSRFMxCzAJBgNVBAgMAldBMTAwLgYDVQQDDCdB +bWF6b24gUkRTIGFwLWVhc3QtMSBSb290IENBIFJTQTQwOTYgRzExEDAOBgNVBAcM +B1NlYXR0bGUwIBcNMjEwNTI1MjEzNDUxWhgPMjEyMTA1MjUyMjM0NTFaMIGXMQsw +CQYDVQQGEwJVUzEiMCAGA1UECgwZQW1hem9uIFdlYiBTZXJ2aWNlcywgSW5jLjET +MBEGA1UECwwKQW1hem9uIFJEUzELMAkGA1UECAwCV0ExMDAuBgNVBAMMJ0FtYXpv +biBSRFMgYXAtZWFzdC0xIFJvb3QgQ0EgUlNBNDA5NiBHMTEQMA4GA1UEBwwHU2Vh +dHRsZTCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBAMyW9kBJjD/hx8e8 +b5E1sF42bp8TXsz1htSYE3Tl3T1Aq379DfEhB+xa/ASDZxt7/vwa81BkNo4M6HYq +okYIXeE7cu5SnSgjWXqcERhgPevtAwgmhdE3yREe8oz2DyOi2qKKZqah+1gpPaIQ +fK0uAqoeQlyHosye3KZZKkDHBatjBsQ5kf8lhuf7wVulEZVRHY2bP2X7N98PfbpL +QdH7mWXzDtJJ0LiwFwds47BrkgK1pkHx2p1mTo+HMkfX0P6Fq1atkVC2RHHtbB/X +iYyH7paaHBzviFrhr679zNqwXIOKlbf74w3mS11P76rFn9rS1BAH2Qm6eY5S/Fxe +HEKXm4kjPN63Zy0p3yE5EjPt54yPkvumOnT+RqDGJ2HCI9k8Ehcbve0ogfdRKNqQ +VHWYTy8V33ndQRHZlx/CuU1yN61TH4WSoMly1+q1ihTX9sApmlQ14B2pJi/9DnKW +cwECrPy1jAowC2UJ45RtC8UC05CbP9yrIy/7Noj8gQDiDOepm+6w1g6aNlWoiuQS +kyI6nzz1983GcnOHya73ga7otXo0Qfg9jPghlYiMomrgshlSLDHZG0Ib/3hb8cnR +1OcN9FpzNmVK2Ll1SmTMLrIhuCkyNYX9O/bOknbcf706XeESxGduSkHEjIw/k1+2 +Atteoq5dT6cwjnJ9hyhiueVlVkiDAgMBAAGjQjBAMA8GA1UdEwEB/wQFMAMBAf8w +HQYDVR0OBBYEFLUI+DD7RJs+0nRnjcwIVWzzYSsFMA4GA1UdDwEB/wQEAwIBhjAN +BgkqhkiG9w0BAQwFAAOCAgEAb1mcCHv4qMQetLGTBH9IxsB2YUUhr5dda0D2BcHr +UtDbfd0VQs4tux6h/6iKwHPx0Ew8fuuYj99WknG0ffgJfNc5/fMspxR/pc1jpdyU +5zMQ+B9wi0lOZPO9uH7/pr+d2odcNEy8zAwqdv/ihsTwLmGP54is9fVbsgzNW1cm +HKAVL2t/Ope+3QnRiRilKCN1lzhav4HHdLlN401TcWRWKbEuxF/FgxSO2Hmx86pj +e726lweCTMmnq/cTsPOVY0WMjs0or3eHDVlyLgVeV5ldyN+ptg3Oit60T05SRa58 +AJPTaVKIcGQ/gKkKZConpu7GDofT67P/ox0YNY57LRbhsx9r5UY4ROgz7WMQ1yoS +Y+19xizm+mBm2PyjMUbfwZUyCxsdKMwVdOq5/UmTmdms+TR8+m1uBHPOTQ2vKR0s +Pd/THSzPuu+d3dbzRyDSLQbHFFneG760CUlD/ZmzFlQjJ89/HmAmz8IyENq+Sjhx +Jgzy+FjVZb8aRUoYLlnffpUpej1n87Ynlr1GrvC4GsRpNpOHlwuf6WD4W0qUTsC/ +C9JO+fBzUj/aWlJzNcLEW6pte1SB+EdkR2sZvWH+F88TxemeDrV0jKJw5R89CDf8 +ZQNfkxJYjhns+YeV0moYjqQdc7tq4i04uggEQEtVzEhRLU5PE83nlh/K2NZZm8Kj +dIA= +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIID/zCCAuegAwIBAgIRAPVSMfFitmM5PhmbaOFoGfUwDQYJKoZIhvcNAQELBQAw +gZcxCzAJBgNVBAYTAlVTMSIwIAYDVQQKDBlBbWF6b24gV2ViIFNlcnZpY2VzLCBJ +bmMuMRMwEQYDVQQLDApBbWF6b24gUkRTMQswCQYDVQQIDAJXQTEwMC4GA1UEAwwn +QW1hem9uIFJEUyB1cy1lYXN0LTEgUm9vdCBDQSBSU0EyMDQ4IEcxMRAwDgYDVQQH +DAdTZWF0dGxlMCAXDTIxMDUyNTIyMzQ1N1oYDzIwNjEwNTI1MjMzNDU3WjCBlzEL +MAkGA1UEBhMCVVMxIjAgBgNVBAoMGUFtYXpvbiBXZWIgU2VydmljZXMsIEluYy4x +EzARBgNVBAsMCkFtYXpvbiBSRFMxCzAJBgNVBAgMAldBMTAwLgYDVQQDDCdBbWF6 +b24gUkRTIHVzLWVhc3QtMSBSb290IENBIFJTQTIwNDggRzExEDAOBgNVBAcMB1Nl +YXR0bGUwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDu9H7TBeGoDzMr +dxN6H8COntJX4IR6dbyhnj5qMD4xl/IWvp50lt0VpmMd+z2PNZzx8RazeGC5IniV +5nrLg0AKWRQ2A/lGGXbUrGXCSe09brMQCxWBSIYe1WZZ1iU1IJ/6Bp4D2YEHpXrW +bPkOq5x3YPcsoitgm1Xh8ygz6vb7PsvJvPbvRMnkDg5IqEThapPjmKb8ZJWyEFEE +QRrkCIRueB1EqQtJw0fvP4PKDlCJAKBEs/y049FoOqYpT3pRy0WKqPhWve+hScMd +6obq8kxTFy1IHACjHc51nrGII5Bt76/MpTWhnJIJrCnq1/Uc3Qs8IVeb+sLaFC8K +DI69Sw6bAgMBAAGjQjBAMA8GA1UdEwEB/wQFMAMBAf8wHQYDVR0OBBYEFE7PCopt +lyOgtXX0Y1lObBUxuKaCMA4GA1UdDwEB/wQEAwIBhjANBgkqhkiG9w0BAQsFAAOC +AQEAFj+bX8gLmMNefr5jRJfHjrL3iuZCjf7YEZgn89pS4z8408mjj9z6Q5D1H7yS +jNETVV8QaJip1qyhh5gRzRaArgGAYvi2/r0zPsy+Tgf7v1KGL5Lh8NT8iCEGGXwF +g3Ir+Nl3e+9XUp0eyyzBIjHtjLBm6yy8rGk9p6OtFDQnKF5OxwbAgip42CD75r/q +p421maEDDvvRFR4D+99JZxgAYDBGqRRceUoe16qDzbMvlz0A9paCZFclxeftAxv6 +QlR5rItMz/XdzpBJUpYhdzM0gCzAzdQuVO5tjJxmXhkSMcDP+8Q+Uv6FA9k2VpUV +E/O5jgpqUJJ2Hc/5rs9VkAPXeA== +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIICrzCCAjWgAwIBAgIQW0yuFCle3uj4vWiGU0SaGzAKBggqhkjOPQQDAzCBlzEL +MAkGA1UEBhMCVVMxIjAgBgNVBAoMGUFtYXpvbiBXZWIgU2VydmljZXMsIEluYy4x +EzARBgNVBAsMCkFtYXpvbiBSRFMxCzAJBgNVBAgMAldBMTAwLgYDVQQDDCdBbWF6 +b24gUkRTIGFmLXNvdXRoLTEgUm9vdCBDQSBFQ0MzODQgRzExEDAOBgNVBAcMB1Nl +YXR0bGUwIBcNMjEwNTE5MTkzNTE2WhgPMjEyMTA1MTkyMDM1MTZaMIGXMQswCQYD +VQQGEwJVUzEiMCAGA1UECgwZQW1hem9uIFdlYiBTZXJ2aWNlcywgSW5jLjETMBEG +A1UECwwKQW1hem9uIFJEUzELMAkGA1UECAwCV0ExMDAuBgNVBAMMJ0FtYXpvbiBS +RFMgYWYtc291dGgtMSBSb290IENBIEVDQzM4NCBHMTEQMA4GA1UEBwwHU2VhdHRs +ZTB2MBAGByqGSM49AgEGBSuBBAAiA2IABDPiKNZSaXs3Un/J/v+LTsFDANHpi7en +oL2qh0u0DoqNzEBTbBjvO23bLN3k599zh6CY3HKW0r2k1yaIdbWqt4upMCRCcUFi +I4iedAmubgzh56wJdoMZztjXZRwDthTkJKNCMEAwDwYDVR0TAQH/BAUwAwEB/zAd +BgNVHQ4EFgQUWbYkcrvVSnAWPR5PJhIzppcAnZIwDgYDVR0PAQH/BAQDAgGGMAoG +CCqGSM49BAMDA2gAMGUCMCESGqpat93CjrSEjE7z+Hbvz0psZTHwqaxuiH64GKUm +mYynIiwpKHyBrzjKBmeDoQIxANGrjIo6/b8Jl6sdIZQI18V0pAyLfLiZjlHVOnhM +MOTVgr82ZuPoEHTX78MxeMnYlw== +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIIECTCCAvGgAwIBAgIRAIbsx8XOl0sgTNiCN4O+18QwDQYJKoZIhvcNAQELBQAw +gZwxCzAJBgNVBAYTAlVTMSIwIAYDVQQKDBlBbWF6b24gV2ViIFNlcnZpY2VzLCBJ +bmMuMRMwEQYDVQQLDApBbWF6b24gUkRTMQswCQYDVQQIDAJXQTE1MDMGA1UEAwws +QW1hem9uIFJEUyBhcC1ub3J0aGVhc3QtMSBSb290IENBIFJTQTIwNDggRzExEDAO +BgNVBAcMB1NlYXR0bGUwIBcNMjEwNTI1MjE1NDU4WhgPMjA2MTA1MjUyMjU0NTha +MIGcMQswCQYDVQQGEwJVUzEiMCAGA1UECgwZQW1hem9uIFdlYiBTZXJ2aWNlcywg +SW5jLjETMBEGA1UECwwKQW1hem9uIFJEUzELMAkGA1UECAwCV0ExNTAzBgNVBAMM +LEFtYXpvbiBSRFMgYXAtbm9ydGhlYXN0LTEgUm9vdCBDQSBSU0EyMDQ4IEcxMRAw +DgYDVQQHDAdTZWF0dGxlMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA +tROxwXWCgn5R9gI/2Ivjzaxc0g95ysBjoJsnhPdJEHQb7w3y2kWrVWU3Y9fOitgb +CEsnEC3PrhRnzNVW0fPsK6kbvOeCmjvY30rdbxbc8h+bjXfGmIOgAkmoULEr6Hc7 +G1Q/+tvv4lEwIs7bEaf+abSZxRJbZ0MBxhbHn7UHHDiMZYvzK+SV1MGCxx7JVhrm +xWu3GC1zZCsGDhB9YqY9eR6PmjbqA5wy8vqbC57dZZa1QVtWIQn3JaRXn+faIzHx +nLMN5CEWihsdmHBXhnRboXprE/OS4MFv1UrQF/XM/h5RBeCywpHePpC+Oe1T3LNC +iP8KzRFrjC1MX/WXJnmOVQIDAQABo0IwQDAPBgNVHRMBAf8EBTADAQH/MB0GA1Ud +DgQWBBS33XbXAUMs1znyZo4B0+B3D68WFTAOBgNVHQ8BAf8EBAMCAYYwDQYJKoZI +hvcNAQELBQADggEBADuadd2EmlpueY2VlrIIPC30QkoA1EOSoCmZgN6124apkoY1 +HiV4r+QNPljN4WP8gmcARnNkS7ZeR4fvWi8xPh5AxQCpiaBMw4gcbTMCuKDV68Pw +P2dZCTMspvR3CDfM35oXCufdtFnxyU6PAyINUqF/wyTHguO3owRFPz64+sk3r2pT +WHmJjG9E7V+KOh0s6REgD17Gqn6C5ijLchSrPUHB0wOIkeLJZndHxN/76h7+zhMt +fFeNxPWHY2MfpcaLjz4UREzZPSB2U9k+y3pW1omCIcl6MQU9itGx/LpQE+H3ZeX2 +M2bdYd5L+ow+bdbGtsVKOuN+R9Dm17YpswF+vyQ= +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +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 +Em4Hy3A= +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIIEBDCCAuygAwIBAgIQFn6AJ+uxaPDpNVx7174CpjANBgkqhkiG9w0BAQsFADCB +mjELMAkGA1UEBhMCVVMxIjAgBgNVBAoMGUFtYXpvbiBXZWIgU2VydmljZXMsIElu +Yy4xEzARBgNVBAsMCkFtYXpvbiBSRFMxCzAJBgNVBAgMAldBMTMwMQYDVQQDDCpB +bWF6b24gUkRTIGlsLWNlbnRyYWwtMSBSb290IENBIFJTQTIwNDggRzExEDAOBgNV +BAcMB1NlYXR0bGUwIBcNMjIxMjAyMjAxNDA4WhgPMjA2MjEyMDIyMTE0MDhaMIGa +MQswCQYDVQQGEwJVUzEiMCAGA1UECgwZQW1hem9uIFdlYiBTZXJ2aWNlcywgSW5j +LjETMBEGA1UECwwKQW1hem9uIFJEUzELMAkGA1UECAwCV0ExMzAxBgNVBAMMKkFt +YXpvbiBSRFMgaWwtY2VudHJhbC0xIFJvb3QgQ0EgUlNBMjA0OCBHMTEQMA4GA1UE +BwwHU2VhdHRsZTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAL2xGTSJ +fXorki/dkkTqdLyv4U1neeFYEyUCPN/HJ7ZloNwhj8RBrHYhZ4qtvUAvN+rs8fUm +L0wmaL69ye61S+CSfDzNwBDGwOzUm/cc1NEJOHCm8XA0unBNBvpJTjsFk2LQ+rz8 +oU0lVV4mjnfGektrTDeADonO1adJvUTYmF6v1wMnykSkp8AnW9EG/6nwcAJuAJ7d +BfaLThm6lfxPdsBNG81DLKi2me2TLQ4yl+vgRKJi2fJWwA77NaDqQuD5upRIcQwt +5noJt2kFFmeiro98ZMMRaDTHAHhJfWkwkw5f2QNIww7T4r85IwbQCgJVRo4m4ZTC +W/1eiEccU2407mECAwEAAaNCMEAwDwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQU +DNhVvGHzKXv0Yh6asK0apP9jJlUwDgYDVR0PAQH/BAQDAgGGMA0GCSqGSIb3DQEB +CwUAA4IBAQCoEVTUY/rF9Zrlpb1Y1hptEguw0i2pCLakcmv3YNj6thsubbGeGx8Z +RjUA/gPKirpoae2HU1y64WEu7akwr6pdTRtXXjbe9NReT6OW/0xAwceSXCOiStqS +cMsWWTGg6BA3uHqad5clqITjDZr1baQ8X8en4SXRBxXyhJXbOkB60HOQeFR9CNeh +pJdrWLeNYXwU0Z59juqdVMGwvDAYdugWUhW2rhafVUXszfRA5c8Izc+E31kq90aY +LmxFXUHUfG0eQOmxmg+Z/nG7yLUdHIFA3id8MRh22hye3KvRdQ7ZVGFni0hG2vQQ +Q01AvD/rhzyjg0czzJKLK9U/RttwdMaV +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +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 +jGKc3k6UNxpg +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +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 +0Ws= +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIIEBDCCAuygAwIBAgIQHK008RFz9XJfPKvg2ONLCjANBgkqhkiG9w0BAQsFADCB +mjELMAkGA1UEBhMCVVMxIjAgBgNVBAoMGUFtYXpvbiBXZWIgU2VydmljZXMsIElu +Yy4xEzARBgNVBAsMCkFtYXpvbiBSRFMxCzAJBgNVBAgMAldBMTMwMQYDVQQDDCpB +bWF6b24gUkRTIG14LWNlbnRyYWwtMSBSb290IENBIFJTQTIwNDggRzExEDAOBgNV +BAcMB1NlYXR0bGUwIBcNMjQwOTMwMTYxMzUwWhgPMjA2NDA5MzAxNzEzNTBaMIGa +MQswCQYDVQQGEwJVUzEiMCAGA1UECgwZQW1hem9uIFdlYiBTZXJ2aWNlcywgSW5j +LjETMBEGA1UECwwKQW1hem9uIFJEUzELMAkGA1UECAwCV0ExMzAxBgNVBAMMKkFt +YXpvbiBSRFMgbXgtY2VudHJhbC0xIFJvb3QgQ0EgUlNBMjA0OCBHMTEQMA4GA1UE +BwwHU2VhdHRsZTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAIbH+1jM +u1AF7AnEnUU64au5HHnWlQZ5PJCRXmiG4q8NGooNG5A6Qd8lIboWnH6DKAfl3AL+ +ihAsLl9biZ/A/wvoNtW/EizjeYXmiuuCyY/Vo/yv78TW7YNspLYbhc35Lt5V+Hi1 +jnKgNf5IAPG4yIXuIYT+9pD6uCcHuoyzsI8f45oUX3o9PJKaBFWccsgIao1cmZhe +9ButRjAPPtEXyHhLoBgObn5D4G/RWyqXZg0xYTUHHAq6a61UPW8oQ1eqc0aqH+o8 +ZCekjwvgiqTRbUpcWm12KviLgZT1J+9noB7ZX5uY9N5FbOsuG92kkFwgS7cEqEXs +AUCm/EdPZyj6dyUCAwEAAaNCMEAwDwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQU +e8h4FODoLdxY0tZkQY5RRjCEQsUwDgYDVR0PAQH/BAQDAgGGMA0GCSqGSIb3DQEB +CwUAA4IBAQA7iEZTjpddHEaTQR3jbR98g8zO5aXKO9gfDmeD1Yuov50rV22gpYgU +Pz2TtFT2ftDmtqKZevzXCS0sE2a1SxP8jTUGTnRlGdMmeKGKfERneit26AQ6A3VW +qTB5Sv11oqTs2bgrM0Zd//DRWboqRuctjTl+AWCmFqTLTxYHefaDW6zHkVL/2jcP +V8MniRMzHKtDKURpl1R9lG3/HvC54XP63pJpmMRwE4b6LUuuwr6AIKKRMCLFIrGh +8g2OMeOMnXawUxQubTLqqT8yMrZwBD2W6yJnHtECJsBVj1rd3EZ/Cq64JbolFk7/ +fVTZlb5aHfj8sie/qmmiP+T45facauKY +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIICtDCCAjqgAwIBAgIRAMyaTlVLN0ndGp4ffwKAfoMwCgYIKoZIzj0EAwMwgZkx +CzAJBgNVBAYTAlVTMSIwIAYDVQQKDBlBbWF6b24gV2ViIFNlcnZpY2VzLCBJbmMu +MRMwEQYDVQQLDApBbWF6b24gUkRTMQswCQYDVQQIDAJXQTEyMDAGA1UEAwwpQW1h +em9uIFJEUyBtZS1jZW50cmFsLTEgUm9vdCBDQSBFQ0MzODQgRzExEDAOBgNVBAcM +B1NlYXR0bGUwIBcNMjIwNTA3MDA0NDM3WhgPMjEyMjA1MDcwMTQ0MzdaMIGZMQsw +CQYDVQQGEwJVUzEiMCAGA1UECgwZQW1hem9uIFdlYiBTZXJ2aWNlcywgSW5jLjET +MBEGA1UECwwKQW1hem9uIFJEUzELMAkGA1UECAwCV0ExMjAwBgNVBAMMKUFtYXpv +biBSRFMgbWUtY2VudHJhbC0xIFJvb3QgQ0EgRUNDMzg0IEcxMRAwDgYDVQQHDAdT +ZWF0dGxlMHYwEAYHKoZIzj0CAQYFK4EEACIDYgAE19nCV1nsI6CohSor13+B25cr +zg+IHdi9Y3L7ziQnHWI6yjBazvnKD+oC71aRRlR8b5YXsYGUQxWzPLHN7EGPcSGv +bzA9SLG1KQYCJaQ0m9Eg/iGrwKWOgylbhVw0bCxoo0IwQDAPBgNVHRMBAf8EBTAD +AQH/MB0GA1UdDgQWBBS4KsknsJXM9+QPEkBdZxUPaLr11zAOBgNVHQ8BAf8EBAMC +AYYwCgYIKoZIzj0EAwMDaAAwZQIxAJaRgrYIEfXQMZQQDxMTYS0azpyWSseQooXo +L3nYq4OHGBgYyQ9gVjvRYWU85PXbfgIwdi82DtANQFkCu+j+BU0JBY/uRKPEeYzo +JG92igKIcXPqCoxIJ7lJbbzmuf73gQu5 +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +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 +Tspdjbg= +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIIECTCCAvGgAwIBAgIRAJZFh4s9aZGzKaTMLrSb4acwDQYJKoZIhvcNAQELBQAw +gZwxCzAJBgNVBAYTAlVTMSIwIAYDVQQKDBlBbWF6b24gV2ViIFNlcnZpY2VzLCBJ +bmMuMRMwEQYDVQQLDApBbWF6b24gUkRTMQswCQYDVQQIDAJXQTE1MDMGA1UEAwws +QW1hem9uIFJEUyBCZXRhIHVzLWVhc3QtMSBSb290IENBIFJTQTIwNDggRzExEDAO +BgNVBAcMB1NlYXR0bGUwIBcNMjEwNTE4MjEyODQxWhgPMjA2MTA1MTgyMjI4NDFa +MIGcMQswCQYDVQQGEwJVUzEiMCAGA1UECgwZQW1hem9uIFdlYiBTZXJ2aWNlcywg +SW5jLjETMBEGA1UECwwKQW1hem9uIFJEUzELMAkGA1UECAwCV0ExNTAzBgNVBAMM +LEFtYXpvbiBSRFMgQmV0YSB1cy1lYXN0LTEgUm9vdCBDQSBSU0EyMDQ4IEcxMRAw +DgYDVQQHDAdTZWF0dGxlMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA +17i2yoU6diep+WrqxIn2CrDEO2NdJVwWTSckx4WMZlLpkQDoymSmkNHjq9ADIApD +A31Cx+843apL7wub8QkFZD0Tk7/ThdHWJOzcAM3ov98QBPQfOC1W5zYIIRP2F+vQ +TRETHQnLcW3rLv0NMk5oQvIKpJoC9ett6aeVrzu+4cU4DZVWYlJUoC/ljWzCluau +8blfW0Vwin6OB7s0HCG5/wijQWJBU5SrP/KAIPeQi1GqG5efbqAXDr/ple0Ipwyo +Xjjl73LenGUgqpANlC9EAT4i7FkJcllLPeK3NcOHjuUG0AccLv1lGsHAxZLgjk/x +z9ZcnVV9UFWZiyJTKxeKPwIDAQABo0IwQDAPBgNVHRMBAf8EBTADAQH/MB0GA1Ud +DgQWBBRWyMuZUo4gxCR3Luf9/bd2AqZ7CjAOBgNVHQ8BAf8EBAMCAYYwDQYJKoZI +hvcNAQELBQADggEBAIqN2DlIKlvDFPO0QUZQVFbsi/tLdYM98/vvzBpttlTGVMyD +gJuQeHVz+MnhGIwoCGOlGU3OOUoIlLAut0+WG74qYczn43oA2gbMd7HoD7oL/IGg +njorBwJVcuuLv2G//SqM3nxGcLRtkRnQ+lvqPxMz9+0fKFUn6QcIDuF0QSfthLs2 +WSiGEPKO9c9RSXdRQ4pXA7c3hXng8P4A2ZmdciPne5Nu4I4qLDGZYRrRLRkNTrOi +TyS6r2HNGUfgF7eOSeKt3NWL+mNChcYj71/Vycf5edeczpUgfnWy9WbPrK1svKyl +aAs2xg+X6O8qB+Mnj2dNBzm+lZIS3sIlm+nO9sg= +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIICrjCCAjSgAwIBAgIRAPAlEk8VJPmEzVRRaWvTh2AwCgYIKoZIzj0EAwMwgZYx +CzAJBgNVBAYTAlVTMSIwIAYDVQQKDBlBbWF6b24gV2ViIFNlcnZpY2VzLCBJbmMu +MRMwEQYDVQQLDApBbWF6b24gUkRTMQswCQYDVQQIDAJXQTEvMC0GA1UEAwwmQW1h +em9uIFJEUyB1cy1lYXN0LTEgUm9vdCBDQSBFQ0MzODQgRzExEDAOBgNVBAcMB1Nl +YXR0bGUwIBcNMjEwNTI1MjI0MTU1WhgPMjEyMTA1MjUyMzQxNTVaMIGWMQswCQYD +VQQGEwJVUzEiMCAGA1UECgwZQW1hem9uIFdlYiBTZXJ2aWNlcywgSW5jLjETMBEG +A1UECwwKQW1hem9uIFJEUzELMAkGA1UECAwCV0ExLzAtBgNVBAMMJkFtYXpvbiBS +RFMgdXMtZWFzdC0xIFJvb3QgQ0EgRUNDMzg0IEcxMRAwDgYDVQQHDAdTZWF0dGxl +MHYwEAYHKoZIzj0CAQYFK4EEACIDYgAEx5xjrup8II4HOJw15NTnS3H5yMrQGlbj +EDA5MMGnE9DmHp5dACIxmPXPMe/99nO7wNdl7G71OYPCgEvWm0FhdvVUeTb3LVnV +BnaXt32Ek7/oxGk1T+Df03C+W0vmuJ+wo0IwQDAPBgNVHRMBAf8EBTADAQH/MB0G +A1UdDgQWBBTGXmqBWN/1tkSea4pNw0oHrjk2UDAOBgNVHQ8BAf8EBAMCAYYwCgYI +KoZIzj0EAwMDaAAwZQIxAIqqZWCSrIkZ7zsv/FygtAusW6yvlL935YAWYPVXU30m +jkMFLM+/RJ9GMvnO8jHfCgIwB+whlkcItzE9CRQ6CsMo/d5cEHDUu/QW6jSIh9BR +OGh9pTYPVkUbBiKPA7lVVhre +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +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 +nWS0 +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIICtDCCAjmgAwIBAgIQKKqVZvk6NsLET+uYv5myCzAKBggqhkjOPQQDAzCBmTEL +MAkGA1UEBhMCVVMxIjAgBgNVBAoMGUFtYXpvbiBXZWIgU2VydmljZXMsIEluYy4x +EzARBgNVBAsMCkFtYXpvbiBSRFMxCzAJBgNVBAgMAldBMTIwMAYDVQQDDClBbWF6 +b24gUkRTIGlsLWNlbnRyYWwtMSBSb290IENBIEVDQzM4NCBHMTEQMA4GA1UEBwwH +U2VhdHRsZTAgFw0yMjEyMDIyMDMyMjBaGA8yMTIyMTIwMjIxMzIyMFowgZkxCzAJ +BgNVBAYTAlVTMSIwIAYDVQQKDBlBbWF6b24gV2ViIFNlcnZpY2VzLCBJbmMuMRMw +EQYDVQQLDApBbWF6b24gUkRTMQswCQYDVQQIDAJXQTEyMDAGA1UEAwwpQW1hem9u +IFJEUyBpbC1jZW50cmFsLTEgUm9vdCBDQSBFQ0MzODQgRzExEDAOBgNVBAcMB1Nl +YXR0bGUwdjAQBgcqhkjOPQIBBgUrgQQAIgNiAASYwfvj8BmvLAP6UkNQ4X4dXBB/ +webBO7swW+8HnFN2DAu+Cn/lpcDpu+dys1JmkVX435lrCH3oZjol0kCDIM1lF4Cv ++78yoY1Jr/YMat22E4iz4AZd9q0NToS7+ZA0r2yjQjBAMA8GA1UdEwEB/wQFMAMB +Af8wHQYDVR0OBBYEFO/8Py16qPr7J2GWpvxlTMB+op7XMA4GA1UdDwEB/wQEAwIB +hjAKBggqhkjOPQQDAwNpADBmAjEAwk+rg788+u8JL6sdix7l57WTo8E/M+o3TO5x +uRuPdShrBFm4ArGR2PPs4zCQuKgqAjEAi0TA3PVqAxKpoz+Ps8/054p9WTgDfBFZ +i/lm2yTaPs0xjY6FNWoy7fsVw5oEKxOn +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIIGCTCCA/GgAwIBAgIRAOY7gfcBZgR2tqfBzMbFQCUwDQYJKoZIhvcNAQEMBQAw +gZwxCzAJBgNVBAYTAlVTMSIwIAYDVQQKDBlBbWF6b24gV2ViIFNlcnZpY2VzLCBJ +bmMuMRMwEQYDVQQLDApBbWF6b24gUkRTMQswCQYDVQQIDAJXQTE1MDMGA1UEAwws +QW1hem9uIFJEUyBhcC1zb3V0aGVhc3QtNCBSb290IENBIFJTQTQwOTYgRzExEDAO +BgNVBAcMB1NlYXR0bGUwIBcNMjIwNTI1MTY1NDU5WhgPMjEyMjA1MjUxNzU0NTla +MIGcMQswCQYDVQQGEwJVUzEiMCAGA1UECgwZQW1hem9uIFdlYiBTZXJ2aWNlcywg +SW5jLjETMBEGA1UECwwKQW1hem9uIFJEUzELMAkGA1UECAwCV0ExNTAzBgNVBAMM +LEFtYXpvbiBSRFMgYXAtc291dGhlYXN0LTQgUm9vdCBDQSBSU0E0MDk2IEcxMRAw +DgYDVQQHDAdTZWF0dGxlMIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEA +lfxER43FuLRdL08bddF0YhbCP+XXKj1A/TFMXmd2My8XDei8rPXFYyyjMig9+xZw +uAsIxLwz8uiA26CKA8bCZKg5VG2kTeOJAfvBJaLv1CZefs3Z4Uf1Sjvm6MF2yqEj +GoORfyfL9HiZFTDuF/hcjWoKYCfMuG6M/wO8IbdICrX3n+BiYQJu/pFO660Mg3h/ +8YBBWYDbHoCiH/vkqqJugQ5BM3OI5nsElW51P1icEEqti4AZ7JmtSv9t7fIFBVyR +oaEyOgpp0sm193F/cDJQdssvjoOnaubsSYm1ep3awZAUyGN/X8MBrPY95d0hLhfH +Ehc5Icyg+hsosBljlAyksmt4hFQ9iBnWIz/ZTfGMck+6p3HVL9RDgvluez+rWv59 +8q7omUGsiPApy5PDdwI/Wt/KtC34/2sjslIJfvgifdAtkRPkhff1WEwER00ADrN9 +eGGInaCpJfb1Rq8cV2n00jxg7DcEd65VR3dmIRb0bL+jWK62ni/WdEyomAOMfmGj +aWf78S/4rasHllWJ+QwnaUYY3u6N8Cgio0/ep4i34FxMXqMV3V0/qXdfhyabi/LM +wCxNo1Dwt+s6OtPJbwO92JL+829QAxydfmaMTeHBsgMPkG7RwAekeuatKGHNsc2Z +x2Q4C2wVvOGAhcHwxfM8JfZs3nDSZJndtVVnFlUY0UECAwEAAaNCMEAwDwYDVR0T +AQH/BAUwAwEB/zAdBgNVHQ4EFgQUpnG7mWazy6k97/tb5iduRB3RXgQwDgYDVR0P +AQH/BAQDAgGGMA0GCSqGSIb3DQEBDAUAA4ICAQCDLqq1Wwa9Tkuv7vxBnIeVvvFF +ecTn+P+wJxl9Qa2ortzqTHZsBDyJO62d04AgBwiDXkJ9a+bthgG0H1J7Xee8xqv1 +xyX2yKj24ygHjspLotKP4eDMdDi5TYq+gdkbPmm9Q69B1+W6e049JVGXvWG8/7kU +igxeuCYwtCCdUPRLf6D8y+1XMGgVv3/DSOHWvTg3MJ1wJ3n3+eve3rjGdRYWZeJu +k21HLSZYzVrCtUsh2YAeLnUbSxVuT2Xr4JehYe9zW5HEQ8Je/OUfnCy9vzoN/ITw +osAH+EBJQey7RxEDqMwCaRefH0yeHFcnOll0OXg/urnQmwbEYzQ1uutJaBPsjU0J +Qf06sMxI7GiB5nPE+CnI2sM6A9AW9kvwexGXpNJiLxF8dvPQthpOKGcYu6BFvRmt +6ctfXd9b7JJoVqMWuf5cCY6ihpk1e9JTlAqu4Eb/7JNyGiGCR40iSLvV28un9wiE +plrdYxwcNYq851BEu3r3AyYWw/UW1AKJ5tM+/Gtok+AphMC9ywT66o/Kfu44mOWm +L3nSLSWEcgfUVgrikpnyGbUnGtgCmHiMlUtNVexcE7OtCIZoVAlCGKNu7tyuJf10 +Qlk8oIIzfSIlcbHpOYoN79FkLoDNc2er4Gd+7w1oPQmdAB0jBJnA6t0OUBPKdDdE +Ufff2jrbfbzECn1ELg== +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +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 +fejbXelUSJSzDIdY +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +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 +7BP7 +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIIGCTCCA/GgAwIBAgIRAN7Y9G9i4I+ZaslPobE7VL4wDQYJKoZIhvcNAQEMBQAw +gZwxCzAJBgNVBAYTAlVTMSIwIAYDVQQKDBlBbWF6b24gV2ViIFNlcnZpY2VzLCBJ +bmMuMRMwEQYDVQQLDApBbWF6b24gUkRTMQswCQYDVQQIDAJXQTE1MDMGA1UEAwws +QW1hem9uIFJEUyBhcC1ub3J0aGVhc3QtMiBSb290IENBIFJTQTQwOTYgRzExEDAO +BgNVBAcMB1NlYXR0bGUwIBcNMjEwNTIwMTYzMzIzWhgPMjEyMTA1MjAxNzMzMjNa +MIGcMQswCQYDVQQGEwJVUzEiMCAGA1UECgwZQW1hem9uIFdlYiBTZXJ2aWNlcywg +SW5jLjETMBEGA1UECwwKQW1hem9uIFJEUzELMAkGA1UECAwCV0ExNTAzBgNVBAMM +LEFtYXpvbiBSRFMgYXAtbm9ydGhlYXN0LTIgUm9vdCBDQSBSU0E0MDk2IEcxMRAw +DgYDVQQHDAdTZWF0dGxlMIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEA +4BEPCiIfiK66Q/qa8k+eqf1Q3qsa6Xuu/fPkpuStXVBShhtXd3eqrM0iT4Xxs420 +Va0vSB3oZ7l86P9zYfa60n6PzRxdYFckYX330aI7L/oFIdaodB/C9szvROI0oLG+ +6RwmIF2zcprH0cTby8MiM7G3v9ykpq27g4WhDC1if2j8giOQL3oHpUaByekZNIHF +dIllsI3RkXmR3xmmxoOxJM1B9MZi7e1CvuVtTGOnSGpNCQiqofehTGwxCN2wFSK8 +xysaWlw48G0VzZs7cbxoXMH9QbMpb4tpk0d+T8JfAPu6uWO9UwCLWWydf0CkmA/+ +D50/xd1t33X9P4FEaPSg5lYbHXzSLWn7oLbrN2UqMLaQrkoEBg/VGvzmfN0mbflw ++T87bJ/VEOVNlG+gepyCTf89qIQVWOjuYMox4sK0PjzZGsYEuYiq1+OUT3vk/e5K +ag1fCcq2Isy4/iwB2xcXrsQ6ljwdk1fc+EmOnjGKrhuOHJY3S+RFv4ToQBsVyYhC +XGaC3EkqIX0xaCpDimxYhFjWhpDXAjG/zJ+hRLDAMCMhl/LPGRk/D1kzSbPmdjpl +lEMK5695PeBvEBTQdBQdOiYgOU3vWU6tzwwHfiM2/wgvess/q0FDAHfJhppbgbb9 +3vgsIUcsvoC5o29JvMsUxsDRvsAfEmMSDGkJoA/X6GECAwEAAaNCMEAwDwYDVR0T +AQH/BAUwAwEB/zAdBgNVHQ4EFgQUgEWm1mZCbGD6ytbwk2UU1aLaOUUwDgYDVR0P +AQH/BAQDAgGGMA0GCSqGSIb3DQEBDAUAA4ICAQBb4+ABTGBGwxK1U/q4g8JDqTQM +1Wh8Oz8yAk4XtPJMAmCctxbd81cRnSnePWw/hxViLVtkZ/GsemvXfqAQyOn1coN7 +QeYSw+ZOlu0j2jEJVynmgsR7nIRqE7QkCyZAU+d2FTJUfmee+IiBiGyFGgxz9n7A +JhBZ/eahBbiuoOik/APW2JWLh0xp0W0GznfJ8lAlaQTyDa8iDXmVtbJg9P9qzkvl +FgPXQttzEOyooF8Pb2LCZO4kUz+1sbU7tHdr2YE+SXxt6D3SBv+Yf0FlvyWLiqVk +GDEOlPPTDSjAWgKnqST8UJ0RDcZK/v1ixs7ayqQJU0GUQm1I7LGTErWXHMnCuHKe +UKYuiSZwmTcJ06NgdhcCnGZgPq13ryMDqxPeltQc3n5eO7f1cL9ERYLDLOzm6A9P +oQ3MfcVOsbHgGHZWaPSeNrQRN9xefqBXH0ZPasgcH9WJdsLlEjVUXoultaHOKx3b +UCCb+d3EfqF6pRT488ippOL6bk7zNubwhRa/+y4wjZtwe3kAX78ACJVcjPobH9jZ +ErySads5zdQeaoee5wRKdp3TOfvuCe4bwLRdhOLCHWzEcXzY3g/6+ppLvNom8o+h +Bh5X26G6KSfr9tqhQ3O9IcbARjnuPbvtJnoPY0gz3EHHGPhy0RNW8i2gl3nUp0ah +PtjwbKW0hYAhIttT0Q== +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIICtzCCAj2gAwIBAgIQQRBQTs6Y3H1DDbpHGta3lzAKBggqhkjOPQQDAzCBmzEL +MAkGA1UEBhMCVVMxIjAgBgNVBAoMGUFtYXpvbiBXZWIgU2VydmljZXMsIEluYy4x +EzARBgNVBAsMCkFtYXpvbiBSRFMxCzAJBgNVBAgMAldBMTQwMgYDVQQDDCtBbWF6 +b24gUkRTIGFwLXNvdXRoZWFzdC0zIFJvb3QgQ0EgRUNDMzg0IEcxMRAwDgYDVQQH +DAdTZWF0dGxlMCAXDTIxMDYxMTAwMTI0M1oYDzIxMjEwNjExMDExMjQzWjCBmzEL +MAkGA1UEBhMCVVMxIjAgBgNVBAoMGUFtYXpvbiBXZWIgU2VydmljZXMsIEluYy4x +EzARBgNVBAsMCkFtYXpvbiBSRFMxCzAJBgNVBAgMAldBMTQwMgYDVQQDDCtBbWF6 +b24gUkRTIGFwLXNvdXRoZWFzdC0zIFJvb3QgQ0EgRUNDMzg0IEcxMRAwDgYDVQQH +DAdTZWF0dGxlMHYwEAYHKoZIzj0CAQYFK4EEACIDYgAEs0942Xj4m/gKA+WA6F5h +AHYuek9eGpzTRoLJddM4rEV1T3eSueytMVKOSlS3Ub9IhyQrH2D8EHsLYk9ktnGR +pATk0kCYTqFbB7onNo070lmMJmGT/Q7NgwC8cySChFxbo0IwQDAPBgNVHRMBAf8E +BTADAQH/MB0GA1UdDgQWBBQ20iKBKiNkcbIZRu0y1uoF1yJTEzAOBgNVHQ8BAf8E +BAMCAYYwCgYIKoZIzj0EAwMDaAAwZQIwYv0wTSrpQTaPaarfLN8Xcqrqu3hzl07n +FrESIoRw6Cx77ZscFi2/MV6AFyjCV/TlAjEAhpwJ3tpzPXpThRML8DMJYZ3YgMh3 +CMuLqhPpla3cL0PhybrD27hJWl29C4el6aMO +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIICrDCCAjOgAwIBAgIQGcztRyV40pyMKbNeSN+vXTAKBggqhkjOPQQDAzCBljEL +MAkGA1UEBhMCVVMxIjAgBgNVBAoMGUFtYXpvbiBXZWIgU2VydmljZXMsIEluYy4x +EzARBgNVBAsMCkFtYXpvbiBSRFMxCzAJBgNVBAgMAldBMS8wLQYDVQQDDCZBbWF6 +b24gUkRTIHVzLWVhc3QtMiBSb290IENBIEVDQzM4NCBHMTEQMA4GA1UEBwwHU2Vh +dHRsZTAgFw0yMTA1MjEyMzE1NTZaGA8yMTIxMDUyMjAwMTU1NlowgZYxCzAJBgNV +BAYTAlVTMSIwIAYDVQQKDBlBbWF6b24gV2ViIFNlcnZpY2VzLCBJbmMuMRMwEQYD +VQQLDApBbWF6b24gUkRTMQswCQYDVQQIDAJXQTEvMC0GA1UEAwwmQW1hem9uIFJE +UyB1cy1lYXN0LTIgUm9vdCBDQSBFQ0MzODQgRzExEDAOBgNVBAcMB1NlYXR0bGUw +djAQBgcqhkjOPQIBBgUrgQQAIgNiAAQfDcv+GGRESD9wT+I5YIPRsD3L+/jsiIis +Tr7t9RSbFl+gYpO7ZbDXvNbV5UGOC5lMJo/SnqFRTC6vL06NF7qOHfig3XO8QnQz +6T5uhhrhnX2RSY3/10d2kTyHq3ZZg3+jQjBAMA8GA1UdEwEB/wQFMAMBAf8wHQYD +VR0OBBYEFLDyD3PRyNXpvKHPYYxjHXWOgfPnMA4GA1UdDwEB/wQEAwIBhjAKBggq +hkjOPQQDAwNnADBkAjB20HQp6YL7CqYD82KaLGzgw305aUKw2aMrdkBR29J183jY +6Ocj9+Wcif9xnRMS+7oCMAvrt03rbh4SU9BohpRUcQ2Pjkh7RoY0jDR4Xq4qzjNr +5UFr3BXpFvACxXF51BksGQ== +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIICrjCCAjWgAwIBAgIQeKbS5zvtqDvRtwr5H48cAjAKBggqhkjOPQQDAzCBlzEL +MAkGA1UEBhMCVVMxIjAgBgNVBAoMGUFtYXpvbiBXZWIgU2VydmljZXMsIEluYy4x +EzARBgNVBAsMCkFtYXpvbiBSRFMxCzAJBgNVBAgMAldBMTAwLgYDVQQDDCdBbWF6 +b24gUkRTIG1lLXNvdXRoLTEgUm9vdCBDQSBFQ0MzODQgRzExEDAOBgNVBAcMB1Nl +YXR0bGUwIBcNMjEwNTIwMTcxOTU1WhgPMjEyMTA1MjAxODE5NTVaMIGXMQswCQYD +VQQGEwJVUzEiMCAGA1UECgwZQW1hem9uIFdlYiBTZXJ2aWNlcywgSW5jLjETMBEG +A1UECwwKQW1hem9uIFJEUzELMAkGA1UECAwCV0ExMDAuBgNVBAMMJ0FtYXpvbiBS +RFMgbWUtc291dGgtMSBSb290IENBIEVDQzM4NCBHMTEQMA4GA1UEBwwHU2VhdHRs +ZTB2MBAGByqGSM49AgEGBSuBBAAiA2IABEKjgUaAPmUlRMEQdBC7BScAGosJ1zRV +LDd38qTBjzgmwBfQJ5ZfGIvyEK5unB09MB4e/3qqK5I/L6Qn5Px/n5g4dq0c7MQZ +u7G9GBYm90U3WRJBf7lQrPStXaRnS4A/O6NCMEAwDwYDVR0TAQH/BAUwAwEB/zAd +BgNVHQ4EFgQUNKcAbGEIn03/vkwd8g6jNyiRdD4wDgYDVR0PAQH/BAQDAgGGMAoG +CCqGSM49BAMDA2cAMGQCMHIeTrjenCSYuGC6txuBt/0ZwnM/ciO9kHGWVCoK8QLs +jGghb5/YSFGZbmQ6qpGlSAIwVOQgdFfTpEfe5i+Vs9frLJ4QKAfc27cTNYzRIM0I +E+AJgK4C4+DiyyMzOpiCfmvq +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +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 +WT1QcNdw7ntUJP/w +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIICrzCCAjWgAwIBAgIQARky6+5PNFRkFVOp3Ob1CTAKBggqhkjOPQQDAzCBlzEL +MAkGA1UEBhMCVVMxIjAgBgNVBAoMGUFtYXpvbiBXZWIgU2VydmljZXMsIEluYy4x +EzARBgNVBAsMCkFtYXpvbiBSRFMxCzAJBgNVBAgMAldBMTAwLgYDVQQDDCdBbWF6 +b24gUkRTIGV1LXNvdXRoLTIgUm9vdCBDQSBFQ0MzODQgRzExEDAOBgNVBAcMB1Nl +YXR0bGUwIBcNMjIwNTIzMTg0MTI4WhgPMjEyMjA1MjMxOTQxMjdaMIGXMQswCQYD +VQQGEwJVUzEiMCAGA1UECgwZQW1hem9uIFdlYiBTZXJ2aWNlcywgSW5jLjETMBEG +A1UECwwKQW1hem9uIFJEUzELMAkGA1UECAwCV0ExMDAuBgNVBAMMJ0FtYXpvbiBS +RFMgZXUtc291dGgtMiBSb290IENBIEVDQzM4NCBHMTEQMA4GA1UEBwwHU2VhdHRs +ZTB2MBAGByqGSM49AgEGBSuBBAAiA2IABNVGL5oF7cfIBxKyWd2PVK/S5yQfaJY3 +QFHWvEdt6951n9JhiiPrHzfVHsxZp1CBjILRMzjgRbYWmc8qRoLkgGE7htGdwudJ +Fa/WuKzO574Prv4iZXUnVGTboC7JdvKbh6NCMEAwDwYDVR0TAQH/BAUwAwEB/zAd +BgNVHQ4EFgQUgDeIIEKynwUbNXApdIPnmRWieZwwDgYDVR0PAQH/BAQDAgGGMAoG +CCqGSM49BAMDA2gAMGUCMEOOJfucrST+FxuqJkMZyCM3gWGZaB+/w6+XUAJC6hFM +uSTY0F44/bERkA4XhH+YGAIxAIpJQBakCA1/mXjsTnQ+0El9ty+LODp8ibkn031c +8DKDS7pR9UK7ZYdR6zFg3ZCjQw== +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIICrjCCAjOgAwIBAgIQJvkWUcYLbnxtuwnyjMmntDAKBggqhkjOPQQDAzCBljEL +MAkGA1UEBhMCVVMxIjAgBgNVBAoMGUFtYXpvbiBXZWIgU2VydmljZXMsIEluYy4x +EzARBgNVBAsMCkFtYXpvbiBSRFMxCzAJBgNVBAgMAldBMS8wLQYDVQQDDCZBbWF6 +b24gUkRTIGV1LXdlc3QtMyBSb290IENBIEVDQzM4NCBHMTEQMA4GA1UEBwwHU2Vh +dHRsZTAgFw0yMTA1MjUyMjI2MTJaGA8yMTIxMDUyNTIzMjYxMlowgZYxCzAJBgNV +BAYTAlVTMSIwIAYDVQQKDBlBbWF6b24gV2ViIFNlcnZpY2VzLCBJbmMuMRMwEQYD +VQQLDApBbWF6b24gUkRTMQswCQYDVQQIDAJXQTEvMC0GA1UEAwwmQW1hem9uIFJE +UyBldS13ZXN0LTMgUm9vdCBDQSBFQ0MzODQgRzExEDAOBgNVBAcMB1NlYXR0bGUw +djAQBgcqhkjOPQIBBgUrgQQAIgNiAARENn8uHCyjn1dFax4OeXxvbV861qsXFD9G +DshumTmFzWWHN/69WN/AOsxy9XN5S7Cgad4gQgeYYYgZ5taw+tFo/jQvCLY//uR5 +uihcLuLJ78opvRPvD9kbWZ6oXfBtFkWjQjBAMA8GA1UdEwEB/wQFMAMBAf8wHQYD +VR0OBBYEFKiK3LpoF+gDnqPldGSwChBPCYciMA4GA1UdDwEB/wQEAwIBhjAKBggq +hkjOPQQDAwNpADBmAjEA+7qfvRlnvF1Aosyp9HzxxCbN7VKu+QXXPhLEBWa5oeWW +UOcifunf/IVLC4/FGCsLAjEAte1AYp+iJyOHDB8UYkhBE/1sxnFaTiEPbvQBU0wZ +SuwWVLhu2wWDuSW+K7tTuL8p +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIID/zCCAuegAwIBAgIRAKeDpqX5WFCGNo94M4v69sUwDQYJKoZIhvcNAQELBQAw +gZcxCzAJBgNVBAYTAlVTMSIwIAYDVQQKDBlBbWF6b24gV2ViIFNlcnZpY2VzLCBJ +bmMuMRMwEQYDVQQLDApBbWF6b24gUkRTMQswCQYDVQQIDAJXQTEwMC4GA1UEAwwn +QW1hem9uIFJEUyBldS13ZXN0LTMgUm9vdCBDQSBSU0EyMDQ4IEcxMRAwDgYDVQQH +DAdTZWF0dGxlMCAXDTIxMDUyNTIyMTgzM1oYDzIwNjEwNTI1MjMxODMzWjCBlzEL +MAkGA1UEBhMCVVMxIjAgBgNVBAoMGUFtYXpvbiBXZWIgU2VydmljZXMsIEluYy4x +EzARBgNVBAsMCkFtYXpvbiBSRFMxCzAJBgNVBAgMAldBMTAwLgYDVQQDDCdBbWF6 +b24gUkRTIGV1LXdlc3QtMyBSb290IENBIFJTQTIwNDggRzExEDAOBgNVBAcMB1Nl +YXR0bGUwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCcKOTEMTfzvs4H +WtJR8gI7GXN6xesulWtZPv21oT+fLGwJ+9Bv8ADCGDDrDxfeH/HxJmzG9hgVAzVn +4g97Bn7q07tGZM5pVi96/aNp11velZT7spOJKfJDZTlGns6DPdHmx48whpdO+dOb +6+eR0VwCIv+Vl1fWXgoACXYCoKjhxJs+R+fwY//0JJ1YG8yjZ+ghLCJmvlkOJmE1 +TCPUyIENaEONd6T+FHGLVYRRxC2cPO65Jc4yQjsXvvQypoGgx7FwD5voNJnFMdyY +754JGPOOe/SZdepN7Tz7UEq8kn7NQSbhmCsgA/Hkjkchz96qN/YJ+H/okiQUTNB0 +eG9ogiVFAgMBAAGjQjBAMA8GA1UdEwEB/wQFMAMBAf8wHQYDVR0OBBYEFFjayw9Y +MjbxfF14XAhMM2VPl0PfMA4GA1UdDwEB/wQEAwIBhjANBgkqhkiG9w0BAQsFAAOC +AQEAAtmx6d9+9CWlMoU0JCirtp4dSS41bBfb9Oor6GQ8WIr2LdfZLL6uES/ubJPE +1Sh5Vu/Zon5/MbqLMVrfniv3UpQIof37jKXsjZJFE1JVD/qQfRzG8AlBkYgHNEiS +VtD4lFxERmaCkY1tjKB4Dbd5hfhdrDy29618ZjbSP7NwAfnwb96jobCmMKgxVGiH +UqsLSiEBZ33b2hI7PJ6iTJnYBWGuiDnsWzKRmheA4nxwbmcQSfjbrNwa93w3caL2 +v/4u54Kcasvcu3yFsUwJygt8z43jsGAemNZsS7GWESxVVlW93MJRn6M+MMakkl9L +tWaXdHZ+KUV7LhfYLb0ajvb40w== +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIIEBDCCAuygAwIBAgIQJ5oxPEjefCsaESSwrxk68DANBgkqhkiG9w0BAQsFADCB +mjELMAkGA1UEBhMCVVMxIjAgBgNVBAoMGUFtYXpvbiBXZWIgU2VydmljZXMsIElu +Yy4xEzARBgNVBAsMCkFtYXpvbiBSRFMxCzAJBgNVBAgMAldBMTMwMQYDVQQDDCpB +bWF6b24gUkRTIGV1LWNlbnRyYWwtMiBSb290IENBIFJTQTIwNDggRzExEDAOBgNV +BAcMB1NlYXR0bGUwIBcNMjIwNjA2MjExNzA1WhgPMjA2MjA2MDYyMjE3MDVaMIGa +MQswCQYDVQQGEwJVUzEiMCAGA1UECgwZQW1hem9uIFdlYiBTZXJ2aWNlcywgSW5j +LjETMBEGA1UECwwKQW1hem9uIFJEUzELMAkGA1UECAwCV0ExMzAxBgNVBAMMKkFt +YXpvbiBSRFMgZXUtY2VudHJhbC0yIFJvb3QgQ0EgUlNBMjA0OCBHMTEQMA4GA1UE +BwwHU2VhdHRsZTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBALTQt5eX +g+VP3BjO9VBkWJhE0GfLrU/QIk32I6WvrnejayTrlup9H1z4QWlXF7GNJrqScRMY +KhJHlcP05aPsx1lYco6pdFOf42ybXyWHHJdShj4A5glU81GTT+VrXGzHSarLmtua +eozkQgPpDsSlPt0RefyTyel7r3Cq+5K/4vyjCTcIqbfgaGwTU36ffjM1LaPCuE4O +nINMeD6YuImt2hU/mFl20FZ+IZQUIFZZU7pxGLqTRz/PWcH8tDDxnkYg7tNuXOeN +JbTpXrw7St50/E9ZQ0llGS+MxJD8jGRAa/oL4G/cwnV8P2OEPVVkgN9xDDQeieo0 +3xkzolkDkmeKOnUCAwEAAaNCMEAwDwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQU +bwu8635iQGQMRanekesORM8Hkm4wDgYDVR0PAQH/BAQDAgGGMA0GCSqGSIb3DQEB +CwUAA4IBAQAgN6LE9mUgjsj6xGCX1afYE69fnmCjjb0rC6eEe1mb/QZNcyw4XBIW +6+zTXo4mjZ4ffoxb//R0/+vdTE7IvaLgfAZgFsLKJCtYDDstXZj8ujQnGR9Pig3R +W+LpNacvOOSJSawNQq0Xrlcu55AU4buyD5VjcICnfF1dqBMnGTnh27m/scd/ZMx/ +kapHZ/fMoK2mAgSX/NvUKF3UkhT85vSSM2BTtET33DzCPDQTZQYxFBa4rFRmFi4c +BLlmIReiCGyh3eJhuUUuYAbK6wLaRyPsyEcIOLMQmZe1+gAFm1+1/q5Ke9ugBmjf +PbTWjsi/lfZ5CdVAhc5lmZj/l5aKqwaS +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIICrjCCAjSgAwIBAgIRAKKPTYKln9L4NTx9dpZGUjowCgYIKoZIzj0EAwMwgZYx +CzAJBgNVBAYTAlVTMSIwIAYDVQQKDBlBbWF6b24gV2ViIFNlcnZpY2VzLCBJbmMu +MRMwEQYDVQQLDApBbWF6b24gUkRTMQswCQYDVQQIDAJXQTEvMC0GA1UEAwwmQW1h +em9uIFJEUyBldS13ZXN0LTIgUm9vdCBDQSBFQ0MzODQgRzExEDAOBgNVBAcMB1Nl +YXR0bGUwIBcNMjEwNTIxMjI1NTIxWhgPMjEyMTA1MjEyMzU1MjFaMIGWMQswCQYD +VQQGEwJVUzEiMCAGA1UECgwZQW1hem9uIFdlYiBTZXJ2aWNlcywgSW5jLjETMBEG +A1UECwwKQW1hem9uIFJEUzELMAkGA1UECAwCV0ExLzAtBgNVBAMMJkFtYXpvbiBS +RFMgZXUtd2VzdC0yIFJvb3QgQ0EgRUNDMzg0IEcxMRAwDgYDVQQHDAdTZWF0dGxl +MHYwEAYHKoZIzj0CAQYFK4EEACIDYgAE/owTReDvaRqdmbtTzXbyRmEpKCETNj6O +hZMKH0F8oU9Tmn8RU7kQQj6xUKEyjLPrFBN7c+26TvrVO1KmJAvbc8bVliiJZMbc +C0yV5PtJTalvlMZA1NnciZuhxaxrzlK1o0IwQDAPBgNVHRMBAf8EBTADAQH/MB0G +A1UdDgQWBBT4i5HaoHtrs7Mi8auLhMbKM1XevDAOBgNVHQ8BAf8EBAMCAYYwCgYI +KoZIzj0EAwMDaAAwZQIxAK9A+8/lFdX4XJKgfP+ZLy5ySXC2E0Spoy12Gv2GdUEZ +p1G7c1KbWVlyb1d6subzkQIwKyH0Naf/3usWfftkmq8SzagicKz5cGcEUaULq4tO +GzA/AMpr63IDBAqkZbMDTCmH +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIICrzCCAjWgAwIBAgIQTgIvwTDuNWQo0Oe1sOPQEzAKBggqhkjOPQQDAzCBlzEL +MAkGA1UEBhMCVVMxIjAgBgNVBAoMGUFtYXpvbiBXZWIgU2VydmljZXMsIEluYy4x +EzARBgNVBAsMCkFtYXpvbiBSRFMxCzAJBgNVBAgMAldBMTAwLgYDVQQDDCdBbWF6 +b24gUkRTIGV1LW5vcnRoLTEgUm9vdCBDQSBFQ0MzODQgRzExEDAOBgNVBAcMB1Nl +YXR0bGUwIBcNMjEwNTI0MjEwNjM4WhgPMjEyMTA1MjQyMjA2MzhaMIGXMQswCQYD +VQQGEwJVUzEiMCAGA1UECgwZQW1hem9uIFdlYiBTZXJ2aWNlcywgSW5jLjETMBEG +A1UECwwKQW1hem9uIFJEUzELMAkGA1UECAwCV0ExMDAuBgNVBAMMJ0FtYXpvbiBS +RFMgZXUtbm9ydGgtMSBSb290IENBIEVDQzM4NCBHMTEQMA4GA1UEBwwHU2VhdHRs +ZTB2MBAGByqGSM49AgEGBSuBBAAiA2IABJuzXLU8q6WwSKXBvx8BbdIi3mPhb7Xo +rNJBfuMW1XRj5BcKH1ZoGaDGw+BIIwyBJg8qNmCK8kqIb4cH8/Hbo3Y+xBJyoXq/ +cuk8aPrxiNoRsKWwiDHCsVxaK9L7GhHHAqNCMEAwDwYDVR0TAQH/BAUwAwEB/zAd +BgNVHQ4EFgQUYgcsdU4fm5xtuqLNppkfTHM2QMYwDgYDVR0PAQH/BAQDAgGGMAoG +CCqGSM49BAMDA2gAMGUCMQDz/Rm89+QJOWJecYAmYcBWCcETASyoK1kbr4vw7Hsg +7Ew3LpLeq4IRmTyuiTMl0gMCMAa0QSjfAnxBKGhAnYxcNJSntUyyMpaXzur43ec0 +3D8npJghwC4DuICtKEkQiI5cSg== +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIIGATCCA+mgAwIBAgIRAORIGqQXLTcbbYT2upIsSnQwDQYJKoZIhvcNAQEMBQAw +gZgxCzAJBgNVBAYTAlVTMSIwIAYDVQQKDBlBbWF6b24gV2ViIFNlcnZpY2VzLCBJ +bmMuMRMwEQYDVQQLDApBbWF6b24gUkRTMQswCQYDVQQIDAJXQTExMC8GA1UEAwwo +QW1hem9uIFJEUyBldS1zb3V0aC0yIFJvb3QgQ0EgUlNBNDA5NiBHMTEQMA4GA1UE +BwwHU2VhdHRsZTAgFw0yMjA1MjMxODM0MjJaGA8yMTIyMDUyMzE5MzQyMlowgZgx +CzAJBgNVBAYTAlVTMSIwIAYDVQQKDBlBbWF6b24gV2ViIFNlcnZpY2VzLCBJbmMu +MRMwEQYDVQQLDApBbWF6b24gUkRTMQswCQYDVQQIDAJXQTExMC8GA1UEAwwoQW1h +em9uIFJEUyBldS1zb3V0aC0yIFJvb3QgQ0EgUlNBNDA5NiBHMTEQMA4GA1UEBwwH +U2VhdHRsZTCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBAPKukwsW2s/h +1k+Hf65pOP0knVBnOnMQyT1mopp2XHGdXznj9xS49S30jYoUnWccyXgD983A1bzu +w4fuJRHg4MFdz/NWTgXvy+zy0Roe83OPIJjUmXnnzwUHQcBa9vl6XUO65iQ3pbSi +fQfNDFXD8cvuXbkezeADoy+iFAlzhXTzV9MD44GTuo9Z3qAXNGHQCrgRSCL7uRYt +t1nfwboCbsVRnElopn2cTigyVXE62HzBUmAw1GTbAZeFAqCn5giBWYAfHwTUldRL +6eEa6atfsS2oPNus4ZENa1iQxXq7ft+pMdNt0qKXTCZiiCZjmLkY0V9kWwHTRRF8 +r+75oSL//3di43QnuSCgjwMRIeWNtMud5jf3eQzSBci+9njb6DrrSUbx7blP0srg +94/C/fYOp/0/EHH34w99Th14VVuGWgDgKahT9/COychLOubXUT6vD1As47S9KxTv +yYleVKwJnF9cVjepODN72fNlEf74BwzgSIhUmhksmZSeJBabrjSUj3pdyo/iRZN/ +CiYz9YPQ29eXHPQjBZVIUqWbOVfdwsx0/Xu5T1e7yyXByQ3/oDulahtcoKPAFQ3J +ee6NJK655MdS7pM9hJnU2Rzu3qZ/GkM6YK7xTlMXVouPUZov/VbiaCKbqYDs8Dg+ +UKdeNXAT6+BMleGQzly1X7vjhgeA8ugVAgMBAAGjQjBAMA8GA1UdEwEB/wQFMAMB +Af8wHQYDVR0OBBYEFJdaPwpCf78UolFTEn6GO85/QwUIMA4GA1UdDwEB/wQEAwIB +hjANBgkqhkiG9w0BAQwFAAOCAgEAWkxHIT3mers5YnZRSVjmpxCLivGj1jMB9VYC +iKqTAeIvD0940L0YaZgivQll5pue8UUcQ6M2uCdVVAsNJdmQ5XHIYiGOknYPtxzO +aO+bnZp7VIZw/vJ49hvH6RreA2bbxYMZO/ossYdcWsWbOKHFrRmAw0AhtK/my51g +obV7eQg+WmlE5Iqc75ycUsoZdc3NimkjBi7LQoNP1HMvlLHlF71UZhQDdq+/WdV7 +0zmg+epkki1LjgMmuPyb+xWuYkFKT1/faX+Xs62hIm5BY+aI4if4RuQ+J//0pOSs +UajrjTo+jLGB8A96jAe8HaFQenbwMjlaHRDAF0wvbkYrMr5a6EbneAB37V05QD0Y +Rh4L4RrSs9DX2hbSmS6iLDuPEjanHKzglF5ePEvnItbRvGGkynqDVlwF+Bqfnw8l +0i8Hr1f1/LP1c075UjkvsHlUnGgPbLqA0rDdcxF8Fdlv1BunUjX0pVlz10Ha5M6P +AdyWUOneOfaA5G7jjv7i9qg3r99JNs1/Lmyg/tV++gnWTAsSPFSSEte81kmPhlK3 +2UtAO47nOdTtk+q4VIRAwY1MaOR7wTFZPfer1mWs4RhKNu/odp8urEY87iIzbMWT +QYO/4I6BGj9rEWNGncvR5XTowwIthMCj2KWKM3Z/JxvjVFylSf+s+FFfO1bNIm6h +u3UBpZI= +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIICtDCCAjmgAwIBAgIQenQbcP/Zbj9JxvZ+jXbRnTAKBggqhkjOPQQDAzCBmTEL +MAkGA1UEBhMCVVMxIjAgBgNVBAoMGUFtYXpvbiBXZWIgU2VydmljZXMsIEluYy4x +EzARBgNVBAsMCkFtYXpvbiBSRFMxCzAJBgNVBAgMAldBMTIwMAYDVQQDDClBbWF6 +b24gUkRTIGV1LWNlbnRyYWwtMSBSb290IENBIEVDQzM4NCBHMTEQMA4GA1UEBwwH +U2VhdHRsZTAgFw0yMTA1MjEyMjMzMjRaGA8yMTIxMDUyMTIzMzMyNFowgZkxCzAJ +BgNVBAYTAlVTMSIwIAYDVQQKDBlBbWF6b24gV2ViIFNlcnZpY2VzLCBJbmMuMRMw +EQYDVQQLDApBbWF6b24gUkRTMQswCQYDVQQIDAJXQTEyMDAGA1UEAwwpQW1hem9u +IFJEUyBldS1jZW50cmFsLTEgUm9vdCBDQSBFQ0MzODQgRzExEDAOBgNVBAcMB1Nl +YXR0bGUwdjAQBgcqhkjOPQIBBgUrgQQAIgNiAATlBHiEM9LoEb1Hdnd5j2VpCDOU +5nGuFoBD8ROUCkFLFh5mHrHfPXwBc63heW9WrP3qnDEm+UZEUvW7ROvtWCTPZdLz +Z4XaqgAlSqeE2VfUyZOZzBSgUUJk7OlznXfkCMOjQjBAMA8GA1UdEwEB/wQFMAMB +Af8wHQYDVR0OBBYEFDT/ThjQZl42Nv/4Z/7JYaPNMly2MA4GA1UdDwEB/wQEAwIB +hjAKBggqhkjOPQQDAwNpADBmAjEAnZWmSgpEbmq+oiCa13l5aGmxSlfp9h12Orvw +Dq/W5cENJz891QD0ufOsic5oGq1JAjEAp5kSJj0MxJBTHQze1Aa9gG4sjHBxXn98 +4MP1VGsQuhfndNHQb4V0Au7OWnOeiobq +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIID/zCCAuegAwIBAgIRAMgnyikWz46xY6yRgiYwZ3swDQYJKoZIhvcNAQELBQAw +gZcxCzAJBgNVBAYTAlVTMSIwIAYDVQQKDBlBbWF6b24gV2ViIFNlcnZpY2VzLCBJ +bmMuMRMwEQYDVQQLDApBbWF6b24gUkRTMQswCQYDVQQIDAJXQTEwMC4GA1UEAwwn +QW1hem9uIFJEUyBldS13ZXN0LTEgUm9vdCBDQSBSU0EyMDQ4IEcxMRAwDgYDVQQH +DAdTZWF0dGxlMCAXDTIxMDUyMDE2NDkxMloYDzIwNjEwNTIwMTc0OTEyWjCBlzEL +MAkGA1UEBhMCVVMxIjAgBgNVBAoMGUFtYXpvbiBXZWIgU2VydmljZXMsIEluYy4x +EzARBgNVBAsMCkFtYXpvbiBSRFMxCzAJBgNVBAgMAldBMTAwLgYDVQQDDCdBbWF6 +b24gUkRTIGV1LXdlc3QtMSBSb290IENBIFJTQTIwNDggRzExEDAOBgNVBAcMB1Nl +YXR0bGUwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCi8JYOc9cYSgZH +gYPxLk6Xcc7HqzamvsnjYU98Dcb98y6iDqS46Ra2Ne02MITtU5MDL+qjxb8WGDZV +RUA9ZS69tkTO3gldW8QdiSh3J6hVNJQW81F0M7ZWgV0gB3n76WCmfT4IWos0AXHM +5v7M/M4tqVmCPViQnZb2kdVlM3/Xc9GInfSMCgNfwHPTXl+PXX+xCdNBePaP/A5C +5S0oK3HiXaKGQAy3K7VnaQaYdiv32XUatlM4K2WS4AMKt+2cw3hTCjlmqKRHvYFQ +veWCXAuc+U5PQDJ9SuxB1buFJZhT4VP3JagOuZbh5NWpIbOTxlAJOb5pGEDuJTKi +1gQQQVEFAgMBAAGjQjBAMA8GA1UdEwEB/wQFMAMBAf8wHQYDVR0OBBYEFNXm+N87 +OFxK9Af/bjSxDCiulGUzMA4GA1UdDwEB/wQEAwIBhjANBgkqhkiG9w0BAQsFAAOC +AQEAkqIbkgZ45spvrgRQ6n9VKzDLvNg+WciLtmVrqyohwwJbj4pYvWwnKQCkVc7c +hUOSBmlSBa5REAPbH5o8bdt00FPRrD6BdXLXhaECKgjsHe1WW08nsequRKD8xVmc +8bEX6sw/utBeBV3mB+3Zv7ejYAbDFM4vnRsWtO+XqgReOgrl+cwdA6SNQT9oW3e5 +rSQ+VaXgJtl9NhkiIysq9BeYigxqS/A13pHQp0COMwS8nz+kBPHhJTsajHCDc8F4 +HfLi6cgs9G0gaRhT8FCH66OdGSqn196sE7Y3bPFFFs/3U+vxvmQgoZC6jegQXAg5 +Prxd+VNXtNI/azitTysQPumH7A== +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIIEBTCCAu2gAwIBAgIRAO8bekN7rUReuNPG8pSTKtEwDQYJKoZIhvcNAQELBQAw +gZoxCzAJBgNVBAYTAlVTMSIwIAYDVQQKDBlBbWF6b24gV2ViIFNlcnZpY2VzLCBJ +bmMuMRMwEQYDVQQLDApBbWF6b24gUkRTMQswCQYDVQQIDAJXQTEzMDEGA1UEAwwq +QW1hem9uIFJEUyBldS1jZW50cmFsLTEgUm9vdCBDQSBSU0EyMDQ4IEcxMRAwDgYD +VQQHDAdTZWF0dGxlMCAXDTIxMDUyMTIyMjM0N1oYDzIwNjEwNTIxMjMyMzQ3WjCB +mjELMAkGA1UEBhMCVVMxIjAgBgNVBAoMGUFtYXpvbiBXZWIgU2VydmljZXMsIElu +Yy4xEzARBgNVBAsMCkFtYXpvbiBSRFMxCzAJBgNVBAgMAldBMTMwMQYDVQQDDCpB +bWF6b24gUkRTIGV1LWNlbnRyYWwtMSBSb290IENBIFJTQTIwNDggRzExEDAOBgNV +BAcMB1NlYXR0bGUwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCTTYds +Tray+Q9VA5j5jTh5TunHKFQzn68ZbOzdqaoi/Rq4ohfC0xdLrxCpfqn2TGDHN6Zi +2qGK1tWJZEd1H0trhzd9d1CtGK+3cjabUmz/TjSW/qBar7e9MA67/iJ74Gc+Ww43 +A0xPNIWcL4aLrHaLm7sHgAO2UCKsrBUpxErOAACERScVYwPAfu79xeFcX7DmcX+e +lIqY16pQAvK2RIzrekSYfLFxwFq2hnlgKHaVgZ3keKP+nmXcXmRSHQYUUr72oYNZ +HcNYl2+gxCc9ccPEHM7xncVEKmb5cWEWvVoaysgQ+osi5f5aQdzgC2X2g2daKbyA +XL/z5FM9GHpS5BJjAgMBAAGjQjBAMA8GA1UdEwEB/wQFMAMBAf8wHQYDVR0OBBYE +FBDAiJ7Py9/A9etNa/ebOnx5l5MGMA4GA1UdDwEB/wQEAwIBhjANBgkqhkiG9w0B +AQsFAAOCAQEALMh/+81fFPdJV/RrJUeoUvFCGMp8iaANu97NpeJyKitNOv7RoeVP +WjivS0KcCqZaDBs+p6IZ0sLI5ZH098LDzzytcfZg0PsGqUAb8a0MiU/LfgDCI9Ee +jsOiwaFB8k0tfUJK32NPcIoQYApTMT2e26lPzYORSkfuntme2PTHUnuC7ikiQrZk +P+SZjWgRuMcp09JfRXyAYWIuix4Gy0eZ4rpRuaTK6mjAb1/LYoNK/iZ/gTeIqrNt +l70OWRsWW8jEmSyNTIubGK/gGGyfuZGSyqoRX6OKHESkP6SSulbIZHyJ5VZkgtXo +2XvyRyJ7w5pFyoofrL3Wv0UF8yt/GDszmg== +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIIF/zCCA+egAwIBAgIRAMDk/F+rrhdn42SfE+ghPC8wDQYJKoZIhvcNAQEMBQAw +gZcxCzAJBgNVBAYTAlVTMSIwIAYDVQQKDBlBbWF6b24gV2ViIFNlcnZpY2VzLCBJ +bmMuMRMwEQYDVQQLDApBbWF6b24gUkRTMQswCQYDVQQIDAJXQTEwMC4GA1UEAwwn +QW1hem9uIFJEUyBldS13ZXN0LTIgUm9vdCBDQSBSU0E0MDk2IEcxMRAwDgYDVQQH +DAdTZWF0dGxlMCAXDTIxMDUyMTIyNTEyMloYDzIxMjEwNTIxMjM1MTIyWjCBlzEL +MAkGA1UEBhMCVVMxIjAgBgNVBAoMGUFtYXpvbiBXZWIgU2VydmljZXMsIEluYy4x +EzARBgNVBAsMCkFtYXpvbiBSRFMxCzAJBgNVBAgMAldBMTAwLgYDVQQDDCdBbWF6 +b24gUkRTIGV1LXdlc3QtMiBSb290IENBIFJTQTQwOTYgRzExEDAOBgNVBAcMB1Nl +YXR0bGUwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQC2twMALVg9vRVu +VNqsr6N8thmp3Dy8jEGTsm3GCQ+C5P2YcGlD/T/5icfWW84uF7Sx3ezcGlvsqFMf +Ukj9sQyqtz7qfFFugyy7pa/eH9f48kWFHLbQYm9GEgbYBIrWMp1cy3vyxuMCwQN4 +DCncqU+yNpy0CprQJEha3PzY+3yJOjDQtc3zr99lyECCFJTDUucxHzyQvX89eL74 +uh8la0lKH3v9wPpnEoftbrwmm5jHNFdzj7uXUHUJ41N7af7z7QUfghIRhlBDiKtx +5lYZemPCXajTc3ryDKUZC/b+B6ViXZmAeMdmQoPE0jwyEp/uaUcdp+FlUQwCfsBk +ayPFEApTWgPiku2isjdeTVmEgL8bJTDUZ6FYFR7ZHcYAsDzcwHgIu3GGEMVRS3Uf +ILmioiyly9vcK4Sa01ondARmsi/I0s7pWpKflaekyv5boJKD/xqwz9lGejmJHelf +8Od2TyqJScMpB7Q8c2ROxBwqwB72jMCEvYigB+Wnbb8RipliqNflIGx938FRCzKL +UQUBmNAznR/yRRL0wHf9UAE/8v9a09uZABeiznzOFAl/frHpgdAbC00LkFlnwwgX +g8YfEFlkp4fLx5B7LtoO6uVNFVimLxtwirpyKoj3G4M/kvSTux8bTw0heBCmWmKR +57MS6k7ODzbv+Kpeht2hqVZCNFMxoQIDAQABo0IwQDAPBgNVHRMBAf8EBTADAQH/ +MB0GA1UdDgQWBBRuMnDhJjoj7DcKALj+HbxEqj3r6jAOBgNVHQ8BAf8EBAMCAYYw +DQYJKoZIhvcNAQEMBQADggIBALSnXfx72C3ldhBP5kY4Mo2DDaGQ8FGpTOOiD95d +0rf7I9LrsBGVqu/Nir+kqqP80PB70+Jy9fHFFigXwcPBX3MpKGxK8Cel7kVf8t1B +4YD6A6bqlzP+OUL0uGWfZpdpDxwMDI2Flt4NEldHgXWPjvN1VblEKs0+kPnKowyg +jhRMgBbD/y+8yg0fIcjXUDTAw/+INcp21gWaMukKQr/8HswqC1yoqW9in2ijQkpK +2RB9vcQ0/gXR0oJUbZQx0jn0OH8Agt7yfMAnJAdnHO4M3gjvlJLzIC5/4aGrRXZl +JoZKfJ2fZRnrFMi0nhAYDeInoS+Rwx+QzaBk6fX5VPyCj8foZ0nmqvuYoydzD8W5 +mMlycgxFqS+DUmO+liWllQC4/MnVBlHGB1Cu3wTj5kgOvNs/k+FW3GXGzD3+rpv0 +QTLuwSbMr+MbEThxrSZRSXTCQzKfehyC+WZejgLb+8ylLJUA10e62o7H9PvCrwj+ +ZDVmN7qj6amzvndCP98sZfX7CFZPLfcBd4wVIjHsFjSNEwWHOiFyLPPG7cdolGKA +lOFvonvo4A1uRc13/zFeP0Xi5n5OZ2go8aOOeGYdI2vB2sgH9R2IASH/jHmr0gvY +0dfBCcfXNgrS0toq0LX/y+5KkKOxh52vEYsJLdhqrveuZhQnsFEm/mFwjRXkyO7c +2jpC +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +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 +FEv9Bg== +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIICsDCCAjagAwIBAgIRAMgApnfGYPpK/fD0dbN2U4YwCgYIKoZIzj0EAwMwgZcx +CzAJBgNVBAYTAlVTMSIwIAYDVQQKDBlBbWF6b24gV2ViIFNlcnZpY2VzLCBJbmMu +MRMwEQYDVQQLDApBbWF6b24gUkRTMQswCQYDVQQIDAJXQTEwMC4GA1UEAwwnQW1h +em9uIFJEUyBldS1zb3V0aC0xIFJvb3QgQ0EgRUNDMzg0IEcxMRAwDgYDVQQHDAdT +ZWF0dGxlMCAXDTIxMDUxOTE4MzgxMVoYDzIxMjEwNTE5MTkzODExWjCBlzELMAkG +A1UEBhMCVVMxIjAgBgNVBAoMGUFtYXpvbiBXZWIgU2VydmljZXMsIEluYy4xEzAR +BgNVBAsMCkFtYXpvbiBSRFMxCzAJBgNVBAgMAldBMTAwLgYDVQQDDCdBbWF6b24g +UkRTIGV1LXNvdXRoLTEgUm9vdCBDQSBFQ0MzODQgRzExEDAOBgNVBAcMB1NlYXR0 +bGUwdjAQBgcqhkjOPQIBBgUrgQQAIgNiAAQfEWl6d4qSuIoECdZPp+39LaKsfsX7 +THs3/RrtT0+h/jl3bjZ7Qc68k16x+HGcHbaayHfqD0LPdzH/kKtNSfQKqemdxDQh +Z4pwkixJu8T1VpXZ5zzCvBXCl75UqgEFS92jQjBAMA8GA1UdEwEB/wQFMAMBAf8w +HQYDVR0OBBYEFFPrSNtWS5JU+Tvi6ABV231XbjbEMA4GA1UdDwEB/wQEAwIBhjAK +BggqhkjOPQQDAwNoADBlAjEA+a7hF1IrNkBd2N/l7IQYAQw8chnRZDzh4wiGsZsC +6A83maaKFWUKIb3qZYXFSi02AjAbp3wxH3myAmF8WekDHhKcC2zDvyOiKLkg9Y6v +ZVmyMR043dscQbcsVoacOYv198c= +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIICtDCCAjqgAwIBAgIRAPhVkIsQ51JFhD2kjFK5uAkwCgYIKoZIzj0EAwMwgZkx +CzAJBgNVBAYTAlVTMSIwIAYDVQQKDBlBbWF6b24gV2ViIFNlcnZpY2VzLCBJbmMu +MRMwEQYDVQQLDApBbWF6b24gUkRTMQswCQYDVQQIDAJXQTEyMDAGA1UEAwwpQW1h +em9uIFJEUyBldS1jZW50cmFsLTIgUm9vdCBDQSBFQ0MzODQgRzExEDAOBgNVBAcM +B1NlYXR0bGUwIBcNMjIwNjA2MjEyOTE3WhgPMjEyMjA2MDYyMjI5MTdaMIGZMQsw +CQYDVQQGEwJVUzEiMCAGA1UECgwZQW1hem9uIFdlYiBTZXJ2aWNlcywgSW5jLjET +MBEGA1UECwwKQW1hem9uIFJEUzELMAkGA1UECAwCV0ExMjAwBgNVBAMMKUFtYXpv +biBSRFMgZXUtY2VudHJhbC0yIFJvb3QgQ0EgRUNDMzg0IEcxMRAwDgYDVQQHDAdT +ZWF0dGxlMHYwEAYHKoZIzj0CAQYFK4EEACIDYgAEA5xnIEBtG5b2nmbj49UEwQza +yX0844fXjccYzZ8xCDUe9dS2XOUi0aZlGblgSe/3lwjg8fMcKXLObGGQfgIx1+5h +AIBjORis/dlyN5q/yH4U5sjS8tcR0GDGVHrsRUZCo0IwQDAPBgNVHRMBAf8EBTAD +AQH/MB0GA1UdDgQWBBRK+lSGutXf4DkTjR3WNfv4+KeNFTAOBgNVHQ8BAf8EBAMC +AYYwCgYIKoZIzj0EAwMDaAAwZQIxAJ4NxQ1Gerqr70ZrnUqc62Vl8NNqTzInamCG +Kce3FTsMWbS9qkgrjZkO9QqOcGIw/gIwSLrwUT+PKr9+H9eHyGvpq9/3AIYSnFkb +Cf3dyWPiLKoAtLFwjzB/CkJlsAS1c8dS +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +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 +r1o= +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIIF/zCCA+egAwIBAgIRAKwYju1QWxUZpn6D1gOtwgQwDQYJKoZIhvcNAQEMBQAw +gZcxCzAJBgNVBAYTAlVTMSIwIAYDVQQKDBlBbWF6b24gV2ViIFNlcnZpY2VzLCBJ +bmMuMRMwEQYDVQQLDApBbWF6b24gUkRTMQswCQYDVQQIDAJXQTEwMC4GA1UEAwwn +QW1hem9uIFJEUyBldS13ZXN0LTEgUm9vdCBDQSBSU0E0MDk2IEcxMRAwDgYDVQQH +DAdTZWF0dGxlMCAXDTIxMDUyMDE2NTM1NFoYDzIxMjEwNTIwMTc1MzU0WjCBlzEL +MAkGA1UEBhMCVVMxIjAgBgNVBAoMGUFtYXpvbiBXZWIgU2VydmljZXMsIEluYy4x +EzARBgNVBAsMCkFtYXpvbiBSRFMxCzAJBgNVBAgMAldBMTAwLgYDVQQDDCdBbWF6 +b24gUkRTIGV1LXdlc3QtMSBSb290IENBIFJTQTQwOTYgRzExEDAOBgNVBAcMB1Nl +YXR0bGUwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQCKdBP1U4lqWWkc +Cb25/BKRTsvNVnISiKocva8GAzJyKfcGRa85gmgu41U+Hz6+39K+XkRfM0YS4BvQ +F1XxWT0bNyypuvwCvmYShSTjN1TY0ltncDddahTajE/4MdSOZb/c98u0yt03cH+G +hVwRyT50h0v/UEol50VfwcVAEZEgcQQYhf1IFUFlIvKpmDOqLuFakOnc7c9akK+i +ivST+JO1tgowbnNkn2iLlSSgUWgb1gjaOsNfysagv1RXdlyPw3EyfwkFifAQvF2P +Q0ayYZfYS640cccv7efM1MSVyFHR9PrrDsF/zr2S2sGPbeHr7R/HwLl+S5J/l9N9 +y0rk6IHAWV4dEkOvgpnuJKURwA48iu1Hhi9e4moNS6eqoK2KmY3VFpuiyWcA73nH +GSmyaH+YuMrF7Fnuu7GEHZL/o6+F5cL3mj2SJJhL7sz0ryf5Cs5R4yN9BIEj/f49 +wh84pM6nexoI0Q4wiSFCxWiBpjSmOK6h7z6+2utaB5p20XDZHhxAlmlx4vMuWtjh +XckgRFxc+ZpVMU3cAHUpVEoO49e/+qKEpPzp8Xg4cToKw2+AfTk3cmyyXQfGwXMQ +ZUHNZ3w9ILMWihGCM2aGUsLcGDRennvNmnmin/SENsOQ8Ku0/a3teEzwV9cmmdYz +5iYs1YtgPvKFobY6+T2RXXh+A5kprwIDAQABo0IwQDAPBgNVHRMBAf8EBTADAQH/ +MB0GA1UdDgQWBBSyUrsQVnKmA8z6/2Ech0rCvqpNmTAOBgNVHQ8BAf8EBAMCAYYw +DQYJKoZIhvcNAQEMBQADggIBAFlj3IFmgiFz5lvTzFTRizhVofhTJsGr14Yfkuc7 +UrXPuXOwJomd4uot2d/VIeGJpfnuS84qGdmQyGewGTJ9inatHsGZgHl9NHNWRwKZ +lTKTbBiq7aqgtUSFa06v202wpzU+1kadxJJePrbABxiXVfOmIW/a1a4hPNcT3syH +FIEg1+CGsp71UNjBuwg3JTKWna0sLSKcxLOSOvX1fzxK5djzVpEsvQMB4PSAzXca +vENgg2ErTwgTA+4s6rRtiBF9pAusN1QVuBahYP3ftrY6f3ycS4K65GnqscyfvKt5 +YgjtEKO3ZeeX8NpubMbzC+0Z6tVKfPFk/9TXuJtwvVeqow0YMrLLyRiYvK7EzJ97 +rrkxoKnHYQSZ+rH2tZ5SE392/rfk1PJL0cdHnkpDkUDO+8cKsFjjYKAQSNC52sKX +74AVh6wMwxYwVZZJf2/2XxkjMWWhKNejsZhUkTISSmiLs+qPe3L67IM7GyKm9/m6 +R3r8x6NGjhTsKH64iYJg7AeKeax4b2e4hBb6GXFftyOs7unpEOIVkJJgM6gh3mwn +R7v4gwFbLKADKt1vHuerSZMiTuNTGhSfCeDM53XI/mjZl2HeuCKP1mCDLlaO+gZR +Q/G+E0sBKgEX4xTkAc3kgkuQGfExdGtnN2U2ehF80lBHB8+2y2E+xWWXih/ZyIcW +wOx+ +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +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 +89cEIGdbjsA= +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIIEADCCAuigAwIBAgIQYjbPSg4+RNRD3zNxO1fuKDANBgkqhkiG9w0BAQsFADCB +mDELMAkGA1UEBhMCVVMxIjAgBgNVBAoMGUFtYXpvbiBXZWIgU2VydmljZXMsIElu +Yy4xEzARBgNVBAsMCkFtYXpvbiBSRFMxCzAJBgNVBAgMAldBMTEwLwYDVQQDDChB +bWF6b24gUkRTIGV1LW5vcnRoLTEgUm9vdCBDQSBSU0EyMDQ4IEcxMRAwDgYDVQQH +DAdTZWF0dGxlMCAXDTIxMDUyNDIwNTkyMVoYDzIwNjEwNTI0MjE1OTIxWjCBmDEL +MAkGA1UEBhMCVVMxIjAgBgNVBAoMGUFtYXpvbiBXZWIgU2VydmljZXMsIEluYy4x +EzARBgNVBAsMCkFtYXpvbiBSRFMxCzAJBgNVBAgMAldBMTEwLwYDVQQDDChBbWF6 +b24gUkRTIGV1LW5vcnRoLTEgUm9vdCBDQSBSU0EyMDQ4IEcxMRAwDgYDVQQHDAdT +ZWF0dGxlMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA179eQHxcV0YL +XMkqEmhSBazHhnRVd8yICbMq82PitE3BZcnv1Z5Zs/oOgNmMkOKae4tCXO/41JCX +wAgbs/eWWi+nnCfpQ/FqbLPg0h3dqzAgeszQyNl9IzTzX4Nd7JFRBVJXPIIKzlRf ++GmFsAhi3rYgDgO27pz3ciahVSN+CuACIRYnA0K0s9lhYdddmrW/SYeWyoB7jPa2 +LmWpAs7bDOgS4LlP2H3eFepBPgNufRytSQUVA8f58lsE5w25vNiUSnrdlvDrIU5n +Qwzc7NIZCx4qJpRbSKWrUtbyJriWfAkGU7i0IoainHLn0eHp9bWkwb9D+C/tMk1X +ERZw2PDGkwIDAQABo0IwQDAPBgNVHRMBAf8EBTADAQH/MB0GA1UdDgQWBBSFmR7s +dAblusFN+xhf1ae0KUqhWTAOBgNVHQ8BAf8EBAMCAYYwDQYJKoZIhvcNAQELBQAD +ggEBAHsXOpjPMyH9lDhPM61zYdja1ebcMVgfUvsDvt+w0xKMKPhBzYDMs/cFOi1N +Q8LV79VNNfI2NuvFmGygcvTIR+4h0pqqZ+wjWl3Kk5jVxCrbHg3RBX02QLumKd/i +kwGcEtTUvTssn3SM8bgM0/1BDXgImZPC567ciLvWDo0s/Fe9dJJC3E0G7d/4s09n +OMdextcxFuWBZrBm/KK3QF0ByA8MG3//VXaGO9OIeeOJCpWn1G1PjT1UklYhkg61 +EbsTiZVA2DLd1BGzfU4o4M5mo68l0msse/ndR1nEY6IywwpgIFue7+rEleDh6b9d +PYkG1rHVw2I0XDG4o17aOn5E94I= +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIIEADCCAuigAwIBAgIQC6W4HFghUkkgyQw14a6JljANBgkqhkiG9w0BAQsFADCB +mDELMAkGA1UEBhMCVVMxIjAgBgNVBAoMGUFtYXpvbiBXZWIgU2VydmljZXMsIElu +Yy4xEzARBgNVBAsMCkFtYXpvbiBSRFMxCzAJBgNVBAgMAldBMTEwLwYDVQQDDChB +bWF6b24gUkRTIGV1LXNvdXRoLTIgUm9vdCBDQSBSU0EyMDQ4IEcxMRAwDgYDVQQH +DAdTZWF0dGxlMCAXDTIyMDUyMzE4MTYzMloYDzIwNjIwNTIzMTkxNjMyWjCBmDEL +MAkGA1UEBhMCVVMxIjAgBgNVBAoMGUFtYXpvbiBXZWIgU2VydmljZXMsIEluYy4x +EzARBgNVBAsMCkFtYXpvbiBSRFMxCzAJBgNVBAgMAldBMTEwLwYDVQQDDChBbWF6 +b24gUkRTIGV1LXNvdXRoLTIgUm9vdCBDQSBSU0EyMDQ4IEcxMRAwDgYDVQQHDAdT +ZWF0dGxlMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAiM/t4FV2R9Nx +UQG203UY83jInTa/6TMq0SPyg617FqYZxvz2kkx09x3dmxepUg9ttGMlPgjsRZM5 +LCFEi1FWk+hxHzt7vAdhHES5tdjwds3aIkgNEillmRDVrUsbrDwufLaa+MMDO2E1 +wQ/JYFXw16WBCCi2g1EtyQ2Xp+tZDX5IWOTnvhZpW8vVDptZ2AcJ5rMhfOYO3OsK +5EF0GGA5ldzuezP+BkrBYGJ4wVKGxeaq9+5AT8iVZrypjwRkD7Y5CurywK3+aBwm +s9Q5Nd8t45JCOUzYp92rFKsCriD86n/JnEvgDfdP6Hvtm0/DkwXK40Wz2q0Zrd0k +mjP054NRPwIDAQABo0IwQDAPBgNVHRMBAf8EBTADAQH/MB0GA1UdDgQWBBRR7yqd +SfKcX2Q8GzhcVucReIpewTAOBgNVHQ8BAf8EBAMCAYYwDQYJKoZIhvcNAQELBQAD +ggEBAEszBRDwXcZyNm07VcFwI1Im94oKwKccuKYeJEsizTBsVon8VpEiMwDs+yGu +3p8kBhvkLwWybkD/vv6McH7T5b9jDX2DoOudqYnnaYeypsPH/00Vh3LvKagqzQza +orWLx+0tLo8xW4BtU+Wrn3JId8LvAhxyYXTn9bm+EwPcStp8xGLwu53OPD1RXYuy +uu+3ps/2piP7GVfou7H6PRaqbFHNfiGg6Y+WA0HGHiJzn8uLmrRJ5YRdIOOG9/xi +qTmAZloUNM7VNuurcMM2hWF494tQpsQ6ysg2qPjbBqzlGoOt3GfBTOZmqmwmqtam +K7juWM/mdMQAJ3SMlE5wI8nVdx4= +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIICrjCCAjSgAwIBAgIRAL9SdzVPcpq7GOpvdGoM80IwCgYIKoZIzj0EAwMwgZYx +CzAJBgNVBAYTAlVTMSIwIAYDVQQKDBlBbWF6b24gV2ViIFNlcnZpY2VzLCBJbmMu +MRMwEQYDVQQLDApBbWF6b24gUkRTMQswCQYDVQQIDAJXQTEvMC0GA1UEAwwmQW1h +em9uIFJEUyBldS13ZXN0LTEgUm9vdCBDQSBFQ0MzODQgRzExEDAOBgNVBAcMB1Nl +YXR0bGUwIBcNMjEwNTIwMTY1ODA3WhgPMjEyMTA1MjAxNzU4MDdaMIGWMQswCQYD +VQQGEwJVUzEiMCAGA1UECgwZQW1hem9uIFdlYiBTZXJ2aWNlcywgSW5jLjETMBEG +A1UECwwKQW1hem9uIFJEUzELMAkGA1UECAwCV0ExLzAtBgNVBAMMJkFtYXpvbiBS +RFMgZXUtd2VzdC0xIFJvb3QgQ0EgRUNDMzg0IEcxMRAwDgYDVQQHDAdTZWF0dGxl +MHYwEAYHKoZIzj0CAQYFK4EEACIDYgAEJWDgXebvwjR+Ce+hxKOLbnsfN5W5dOlP +Zn8kwWnD+SLkU81Eac/BDJsXGrMk6jFD1vg16PEkoSevsuYWlC8xR6FmT6F6pmeh +fsMGOyJpfK4fyoEPhKeQoT23lFIc5Orjo0IwQDAPBgNVHRMBAf8EBTADAQH/MB0G +A1UdDgQWBBSVNAN1CHAz0eZ77qz2adeqjm31TzAOBgNVHQ8BAf8EBAMCAYYwCgYI +KoZIzj0EAwMDaAAwZQIxAMlQeHbcjor49jqmcJ9gRLWdEWpXG8thIf6zfYQ/OEAg +d7GDh4fR/OUk0VfjsBUN/gIwZB0bGdXvK38s6AAE/9IT051cz/wMe9GIrX1MnL1T +1F5OqnXJdiwfZRRTHsRQ/L00 +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +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 +1YfpdH2jp30= +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIID/jCCAuagAwIBAgIQTDc+UgTRtYO7ZGTQ8UWKDDANBgkqhkiG9w0BAQsFADCB +lzELMAkGA1UEBhMCVVMxIjAgBgNVBAoMGUFtYXpvbiBXZWIgU2VydmljZXMsIElu +Yy4xEzARBgNVBAsMCkFtYXpvbiBSRFMxCzAJBgNVBAgMAldBMTAwLgYDVQQDDCdB +bWF6b24gUkRTIGV1LXdlc3QtMiBSb290IENBIFJTQTIwNDggRzExEDAOBgNVBAcM +B1NlYXR0bGUwIBcNMjEwNTIxMjI0NjI0WhgPMjA2MTA1MjEyMzQ2MjRaMIGXMQsw +CQYDVQQGEwJVUzEiMCAGA1UECgwZQW1hem9uIFdlYiBTZXJ2aWNlcywgSW5jLjET +MBEGA1UECwwKQW1hem9uIFJEUzELMAkGA1UECAwCV0ExMDAuBgNVBAMMJ0FtYXpv +biBSRFMgZXUtd2VzdC0yIFJvb3QgQ0EgUlNBMjA0OCBHMTEQMA4GA1UEBwwHU2Vh +dHRsZTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAM1oGtthQ1YiVIC2 +i4u4swMAGxAjc/BZp0yq0eP5ZQFaxnxs7zFAPabEWsrjeDzrRhdVO0h7zskrertP +gblGhfD20JfjvCHdP1RUhy/nzG+T+hn6Takan/GIgs8grlBMRHMgBYHW7tklhjaH +3F7LujhceAHhhgp6IOrpb6YTaTTaJbF3GTmkqxSJ3l1LtEoWz8Al/nL/Ftzxrtez +Vs6ebpvd7sw37sxmXBWX2OlvUrPCTmladw9OrllGXtCFw4YyLe3zozBlZ3cHzQ0q +lINhpRcajTMfZrsiGCkQtoJT+AqVJPS2sHjqsEH8yiySW9Jbq4zyMbM1yqQ2vnnx +MJgoYMcCAwEAAaNCMEAwDwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQUaQG88UnV +JPTI+Pcti1P+q3H7pGYwDgYDVR0PAQH/BAQDAgGGMA0GCSqGSIb3DQEBCwUAA4IB +AQBAkgr75V0sEJimC6QRiTVWEuj2Khy7unjSfudbM6zumhXEU2/sUaVLiYy6cA/x +3v0laDle6T07x9g64j5YastE/4jbzrGgIINFlY0JnaYmR3KZEjgi1s1fkRRf3llL +PJm9u4Q1mbwAMQK/ZjLuuRcL3uRIHJek18nRqT5h43GB26qXyvJqeYYpYfIjL9+/ +YiZAbSRRZG+Li23cmPWrbA1CJY121SB+WybCbysbOXzhD3Sl2KSZRwSw4p2HrFtV +1Prk0dOBtZxCG9luf87ultuDZpfS0w6oNBAMXocgswk24ylcADkkFxBWW+7BETn1 +EpK+t1Lm37mU4sxtuha00XAi +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIIEADCCAuigAwIBAgIQcY44/8NUvBwr6LlHfRy7KjANBgkqhkiG9w0BAQsFADCB +mDELMAkGA1UEBhMCVVMxIjAgBgNVBAoMGUFtYXpvbiBXZWIgU2VydmljZXMsIElu +Yy4xEzARBgNVBAsMCkFtYXpvbiBSRFMxCzAJBgNVBAgMAldBMTEwLwYDVQQDDChB +bWF6b24gUkRTIGV1LXNvdXRoLTEgUm9vdCBDQSBSU0EyMDQ4IEcxMRAwDgYDVQQH +DAdTZWF0dGxlMCAXDTIxMDUxOTE4MjcxOFoYDzIwNjEwNTE5MTkyNzE4WjCBmDEL +MAkGA1UEBhMCVVMxIjAgBgNVBAoMGUFtYXpvbiBXZWIgU2VydmljZXMsIEluYy4x +EzARBgNVBAsMCkFtYXpvbiBSRFMxCzAJBgNVBAgMAldBMTEwLwYDVQQDDChBbWF6 +b24gUkRTIGV1LXNvdXRoLTEgUm9vdCBDQSBSU0EyMDQ4IEcxMRAwDgYDVQQHDAdT +ZWF0dGxlMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA0UaBeC+Usalu +EtXnV7+PnH+gi7/71tI/jkKVGKuhD2JDVvqLVoqbMHRh3+wGMvqKCjbHPcC2XMWv +566fpAj4UZ9CLB5fVzss+QVNTl+FH2XhEzigopp+872ajsNzcZxrMkifxGb4i0U+ +t0Zi+UrbL5tsfP2JonKR1crOrbS6/DlzHBjIiJazGOQcMsJjNuTOItLbMohLpraA +/nApa3kOvI7Ufool1/34MG0+wL3UUA4YkZ6oBJVxjZvvs6tI7Lzz/SnhK2widGdc +snbLqBpHNIZQSorVoiwcFaRBGYX/uzYkiw44Yfa4cK2V/B5zgu1Fbr0gbI2am4eh +yVYyg4jPawIDAQABo0IwQDAPBgNVHRMBAf8EBTADAQH/MB0GA1UdDgQWBBS9gM1m +IIjyh9O5H/7Vj0R/akI7UzAOBgNVHQ8BAf8EBAMCAYYwDQYJKoZIhvcNAQELBQAD +ggEBAF0Sm9HC2AUyedBVnwgkVXMibnYChOzz7T+0Y+fOLXYAEXex2s8oqGeZdGYX +JHkjBn7JXu7LM+TpTbPbFFDoc1sgMguD/ls+8XsqAl1CssW+amryIL+jfcfbgQ+P +ICwEUD9hGdjBgJ5WcuS+qqxHsEIlFNci3HxcxfBa9VsWs5TjI7Vsl4meL5lf7ZyL +wDV7dHRuU+cImqG1MIvPRIlvPnT7EghrCYi2VCPhP2pM/UvShuwVnkz4MJ29ebIk +WR9kpblFxFdE92D5UUvMCjC2kmtgzNiErvTcwIvOO9YCbBHzRB1fFiWrXUHhJWq9 +IkaxR5icb/IpAV0A1lYZEWMVsfQ= +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +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 +rM2p0kk= +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIIECDCCAvCgAwIBAgIQYWF2Yb4HiQV2OJlcN2BhUDANBgkqhkiG9w0BAQsFADCB +nDELMAkGA1UEBhMCVVMxIjAgBgNVBAoMGUFtYXpvbiBXZWIgU2VydmljZXMsIElu +Yy4xEzARBgNVBAsMCkFtYXpvbiBSRFMxCzAJBgNVBAgMAldBMTUwMwYDVQQDDCxB +bWF6b24gUkRTIGFwLXNvdXRoZWFzdC02IFJvb3QgQ0EgUlNBMjA0OCBHMTEQMA4G +A1UEBwwHU2VhdHRsZTAgFw0yNTA1MjAwMTQzNTdaGA8yMDY1MDUyMDAyNDM1N1ow +gZwxCzAJBgNVBAYTAlVTMSIwIAYDVQQKDBlBbWF6b24gV2ViIFNlcnZpY2VzLCBJ +bmMuMRMwEQYDVQQLDApBbWF6b24gUkRTMQswCQYDVQQIDAJXQTE1MDMGA1UEAwws +QW1hem9uIFJEUyBhcC1zb3V0aGVhc3QtNiBSb290IENBIFJTQTIwNDggRzExEDAO +BgNVBAcMB1NlYXR0bGUwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCp +7R+Rclm+GQyjwytLA5lg6AH6IzXEsp8Q5ZtYzjYizhzOWhdqYfYQsv43IJjzY3Cd +Wwtp+/+WOegfFntFfEN7Cy2zP+Ib18TT4alkalo45mbTjcKAflDhLuwLZCrXq7Sk +ECm0lveumAlVXXUW0mFEOd48XvTzvY1tNO8hGlN2l1n2wG6TfOU317q+PLF2F79J +iAqVt2ZUIvFgwIS3qfQK0n6c5OKzPrpsvfecFZePhL20YH/2yCNxGb6rNBRErXxV +oNICqsl03LNOcgpm1WvXINWLJT/le6IEAbHMgMeG3DIG3q4sYiEyE5qi8SSR6S5P +ky3+mtWZL/ZWp2HFQ8xZAgMBAAGjQjBAMA8GA1UdEwEB/wQFMAMBAf8wHQYDVR0O +BBYEFAlH8qgMxdi5cVGO+FTynv4hC3sOMA4GA1UdDwEB/wQEAwIBhjANBgkqhkiG +9w0BAQsFAAOCAQEAS+oW1l/6C3lh4WWE2aBGCdC713mjpBjgTjzpUnyIdQDaBuzD +vHpm+p6bK+a4WP/jtjniUvZhyYD9VbaQhHYa9nSX+LHIkF1cxMTqHMjK8dgnenF7 +KhUbmaERlk7MPAGLoRKngoS8NRjizFiBLUTKn62KvI4DKiswxWJklpd3HIn9qq8q +eR2PLoHHc4WJ/E1UXpCIt9dbWwB22xHp0Gb0vFcdWE76uwR5C4x8eAqct8455NrC +gh3UCCsNEu57aUjNRA+PwvCUDEsJj72mKCzUNGG4bTDCqY1DMlx2TaXByxs7Ctwx +wiK/vWV+ZChc5mMDzf9csrOQzDPPJpiUfp11rg== +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIIGCTCCA/GgAwIBAgIRAKEoQBLrmKhwHJppfnj9miEwDQYJKoZIhvcNAQEMBQAw +gZwxCzAJBgNVBAYTAlVTMSIwIAYDVQQKDBlBbWF6b24gV2ViIFNlcnZpY2VzLCBJ +bmMuMRMwEQYDVQQLDApBbWF6b24gUkRTMQswCQYDVQQIDAJXQTE1MDMGA1UEAwws +QW1hem9uIFJEUyBhcC1zb3V0aGVhc3QtNiBSb290IENBIFJTQTQwOTYgRzExEDAO +BgNVBAcMB1NlYXR0bGUwIBcNMjUwNTIwMDE0NDAyWhgPMjEyNTA1MjAwMjQ0MDJa +MIGcMQswCQYDVQQGEwJVUzEiMCAGA1UECgwZQW1hem9uIFdlYiBTZXJ2aWNlcywg +SW5jLjETMBEGA1UECwwKQW1hem9uIFJEUzELMAkGA1UECAwCV0ExNTAzBgNVBAMM +LEFtYXpvbiBSRFMgYXAtc291dGhlYXN0LTYgUm9vdCBDQSBSU0E0MDk2IEcxMRAw +DgYDVQQHDAdTZWF0dGxlMIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEA +m5LnhN6bm6BRlvFolsYe4EuK0uIWhLFItpCs4GbfAyQMJUEFc9pUIayg9+N6/B7q +4VVRvIDYfqEIjywkWaJwBu58/B2EE47iLTHvAU9HyxojKvucFFHRmrrsQG5Fyn/0 +LNDDiOpik6BTKp4b32JnEe+X5ASd4pU7ibOSec6m+mISIEIAsjaRw8WdcrglJpIu +69NKoM9QJRkiECWe6MSxwuTdNgh5SouuABThg7ROPrxlxt1pkwlN1nA1Czb2GrBe +XV4Be1h1djEC7UoehaVxOs4VFJfOuHg72YHHf3zVhtpJAGR0/HypOcFnXd+DXoXL +wjMUm3mDNyMEJLHQUwYA99QheEl7+aprpe9gM3RQ85sg5XQJ3YUOPuErqDnnVveH +bckOkkTYTfSZUY9fpQmX7YxFv/QN6UpMXh7pzxOeM+VTvsH3JH0HweFNWCSALcbg +/no7Z8mIkOEE9gMCzqoFz0uILQzFM/n5yrAnmCzU+EM5HC3+oxO9mGGwiNp/LQUY +nWqkHHLQjurxmqlLQoBofpYiyi9vdh3IbULtYZJTUT87OA5BsKOH5Vaq6J9vw701 +1zCQnpg8B6tp7+Z2/vCRr1sY7tNmh8gQoiwFfx9Qpkc83/mlyoenvqeevQ42bok8 +0KZvJ/dYKNRZLO7kX8kHZTWkzGOYpfI5djYCWAAlPRUCAwEAAaNCMEAwDwYDVR0T +AQH/BAUwAwEB/zAdBgNVHQ4EFgQUri9+CM+fxgpdvEDFDhb0smAKHy4wDgYDVR0P +AQH/BAQDAgGGMA0GCSqGSIb3DQEBDAUAA4ICAQAUOKOXItwvve85tuPTLBNVP18f +40rf8RPhH1JFKMePXcQ2gav7t9KwxnUk6iqVo/Pt/knUeXJ+uLMRHfjzVpadlUoT +6zH+TndeljwG1QFuYOxwYI+VNwZnb7lF0O0cADkhDfc8/QvG/ZkrAx7s2kWWC8by +fWpsjDt3xDD3ump+Qr+o0jpZyXPbWZQeIo5wmnMOc0PXxeFlIOCeUaJDtaJMRHBq +CpdPT5xkNaRJnZzKJiUZx3NZe4du5lVuz5gT9YmooFGRFHU26hANkHQGxsDTkTx9 +Mg+5QXeDFQ7MnvFmFu2rb2cizZuKL9ZpiLE5f2/Hcd7apY7z8dSJhDWhvmTeeJPr +GbplpepGVt5BIhg395guJ8Kww4Ux7a/8ERdQHkRd+ykspQNfveqLl+IP8s/snLDP +lcILGYvHLq02HGrM8chU5mzNe5lyYmSI3cYxEZdDMiuOVpLlNuNUGadyafFx13Y0 +a6wDpXcot9Qh6W+JFpq0TbgbVezkk+1YzDJK7ShBQqckPlGHl9uyWjX3xuC4KD6g +epx7UmkEP56BhvLMoT+o3KDBts9ujuJIZt+uGoo+rYIZaSuGLKdA60B4ZS3ONrws +G3+o22y0lMvHGMHiwCIH+RQQBt5brmfkjD+QJh5K/1iJaZtFecgeuZnuar5W37k2 +IGstLzLjZODMnBQoSA== +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIICtzCCAj2gAwIBAgIQbliDBFi6VBnhtzveX+/ALjAKBggqhkjOPQQDAzCBmzEL +MAkGA1UEBhMCVVMxIjAgBgNVBAoMGUFtYXpvbiBXZWIgU2VydmljZXMsIEluYy4x +EzARBgNVBAsMCkFtYXpvbiBSRFMxCzAJBgNVBAgMAldBMTQwMgYDVQQDDCtBbWF6 +b24gUkRTIGFwLXNvdXRoZWFzdC02IFJvb3QgQ0EgRUNDMzg0IEcxMRAwDgYDVQQH +DAdTZWF0dGxlMCAXDTI1MDUyMDAxNDQwN1oYDzIxMjUwNTIwMDI0NDA3WjCBmzEL +MAkGA1UEBhMCVVMxIjAgBgNVBAoMGUFtYXpvbiBXZWIgU2VydmljZXMsIEluYy4x +EzARBgNVBAsMCkFtYXpvbiBSRFMxCzAJBgNVBAgMAldBMTQwMgYDVQQDDCtBbWF6 +b24gUkRTIGFwLXNvdXRoZWFzdC02IFJvb3QgQ0EgRUNDMzg0IEcxMRAwDgYDVQQH +DAdTZWF0dGxlMHYwEAYHKoZIzj0CAQYFK4EEACIDYgAEf1cgi4GHIBFSOQMW6NPm +/MUlTNs34ONxLDenJ/m3tkuMjoDSrimTAwQcCoIUrlwOpauaW4le0aFb+SUw6IEW +axCohHeASRW5P7rV/MBgiSWcKxgAj0XSWXaGMXM9ycfso0IwQDAPBgNVHRMBAf8E +BTADAQH/MB0GA1UdDgQWBBTTHGkZp0gQNJj2xYOEKgB+WoGw/zAOBgNVHQ8BAf8E +BAMCAYYwCgYIKoZIzj0EAwMDaAAwZQIwSZoT6sdk5Duk2oqvI9kbYDH84vut3CmX +EtJwpeusN5Uv0fm//WfNwXZmZ3RB7L9qAjEAqFQK/AKtmyTePw4w+LQVljNOh1UQ +sfb8fO9Mt9eYGJ0i1QYo60u/qIypql6ZsIvW +-----END CERTIFICATE----- diff --git a/crates/alien-bindings/src/providers/postgres/ca/azure-postgres-roots.pem b/crates/alien-bindings/src/providers/postgres/ca/azure-postgres-roots.pem new file mode 100644 index 000000000..39d910094 --- /dev/null +++ b/crates/alien-bindings/src/providers/postgres/ca/azure-postgres-roots.pem @@ -0,0 +1,77 @@ +-----BEGIN CERTIFICATE----- +MIIDjjCCAnagAwIBAgIQAzrx5qcRqaC7KGSxHQn65TANBgkqhkiG9w0BAQsFADBh +MQswCQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3 +d3cuZGlnaWNlcnQuY29tMSAwHgYDVQQDExdEaWdpQ2VydCBHbG9iYWwgUm9vdCBH +MjAeFw0xMzA4MDExMjAwMDBaFw0zODAxMTUxMjAwMDBaMGExCzAJBgNVBAYTAlVT +MRUwEwYDVQQKEwxEaWdpQ2VydCBJbmMxGTAXBgNVBAsTEHd3dy5kaWdpY2VydC5j +b20xIDAeBgNVBAMTF0RpZ2lDZXJ0IEdsb2JhbCBSb290IEcyMIIBIjANBgkqhkiG +9w0BAQEFAAOCAQ8AMIIBCgKCAQEAuzfNNNx7a8myaJCtSnX/RrohCgiN9RlUyfuI +2/Ou8jqJkTx65qsGGmvPrC3oXgkkRLpimn7Wo6h+4FR1IAWsULecYxpsMNzaHxmx +1x7e/dfgy5SDN67sH0NO3Xss0r0upS/kqbitOtSZpLYl6ZtrAGCSYP9PIUkY92eQ +q2EGnI/yuum06ZIya7XzV+hdG82MHauVBJVJ8zUtluNJbd134/tJS7SsVQepj5Wz +tCO7TG1F8PapspUwtP1MVYwnSlcUfIKdzXOS0xZKBgyMUNGPHgm+F6HmIcr9g+UQ +vIOlCsRnKPZzFBQ9RnbDhxSJITRNrw9FDKZJobq7nMWxM4MphQIDAQABo0IwQDAP +BgNVHRMBAf8EBTADAQH/MA4GA1UdDwEB/wQEAwIBhjAdBgNVHQ4EFgQUTiJUIBiV +5uNu5g/6+rkS7QYXjzkwDQYJKoZIhvcNAQELBQADggEBAGBnKJRvDkhj6zHd6mcY +1Yl9PMWLSn/pvtsrF9+wX3N3KjITOYFnQoQj8kVnNeyIv/iPsGEMNKSuIEyExtv4 +NeF22d+mQrvHRAiGfzZ0JFrabA0UWTW98kndth/Jsw1HKj2ZL7tcu7XUIOGZX1NG +Fdtom/DzMNU+MeKNhJ7jitralj41E6Vf8PlwUHBHQRFXGU7Aj64GxJUTFy8bJZ91 +8rGOmaFvE7FBcf6IKshPECBV1/MUReXgRPTqh5Uykw7+U0b6LJ3/iyK5S9kJRaTe +pLiaWN0bfVKfjllDiIGknibVb63dDcY3fe0Dkhvld1927jyNxF1WW6LZZm6zNTfl +MrY= +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIIFqDCCA5CgAwIBAgIQHtOXCV/YtLNHcB6qvn9FszANBgkqhkiG9w0BAQwFADBl +MQswCQYDVQQGEwJVUzEeMBwGA1UEChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMTYw +NAYDVQQDEy1NaWNyb3NvZnQgUlNBIFJvb3QgQ2VydGlmaWNhdGUgQXV0aG9yaXR5 +IDIwMTcwHhcNMTkxMjE4MjI1MTIyWhcNNDIwNzE4MjMwMDIzWjBlMQswCQYDVQQG +EwJVUzEeMBwGA1UEChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMTYwNAYDVQQDEy1N +aWNyb3NvZnQgUlNBIFJvb3QgQ2VydGlmaWNhdGUgQXV0aG9yaXR5IDIwMTcwggIi +MA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQDKW76UM4wplZEWCpW9R2LBifOZ +Nt9GkMml7Xhqb0eRaPgnZ1AzHaGm++DlQ6OEAlcBXZxIQIJTELy/xztokLaCLeX0 +ZdDMbRnMlfl7rEqUrQ7eS0MdhweSE5CAg2Q1OQT85elss7YfUJQ4ZVBcF0a5toW1 +HLUX6NZFndiyJrDKxHBKrmCk3bPZ7Pw71VdyvD/IybLeS2v4I2wDwAW9lcfNcztm +gGTjGqwu+UcF8ga2m3P1eDNbx6H7JyqhtJqRjJHTOoI+dkC0zVJhUXAoP8XFWvLJ +jEm7FFtNyP9nTUwSlq31/niol4fX/V4ggNyhSyL71Imtus5Hl0dVe49FyGcohJUc +aDDv70ngNXtk55iwlNpNhTs+VcQor1fznhPbRiefHqJeRIOkpcrVE7NLP8TjwuaG +YaRSMLl6IE9vDzhTyzMMEyuP1pq9KsgtsRx9S1HKR9FIJ3Jdh+vVReZIZZ2vUpC6 +W6IYZVcSn2i51BVrlMRpIpj0M+Dt+VGOQVDJNE92kKz8OMHY4Xu54+OU4UZpyw4K +UGsTuqwPN1q3ErWQgR5WrlcihtnJ0tHXUeOrO8ZV/R4O03QK0dqq6mm4lyiPSMQH ++FJDOvTKVTUssKZqwJz58oHhEmrARdlns87/I6KJClTUFLkqqNfs+avNJVgyeY+Q +W5g5xAgGwax/Dj0ApQIDAQABo1QwUjAOBgNVHQ8BAf8EBAMCAYYwDwYDVR0TAQH/ +BAUwAwEB/zAdBgNVHQ4EFgQUCctZf4aycI8awznjwNnpv7tNsiMwEAYJKwYBBAGC +NxUBBAMCAQAwDQYJKoZIhvcNAQEMBQADggIBAKyvPl3CEZaJjqPnktaXFbgToqZC +LgLNFgVZJ8og6Lq46BrsTaiXVq5lQ7GPAJtSzVXNUzltYkyLDVt8LkS/gxCP81OC +gMNPOsduET/m4xaRhPtthH80dK2Jp86519efhGSSvpWhrQlTM93uCupKUY5vVau6 +tZRGrox/2KJQJWVggEbbMwSubLWYdFQl3JPk+ONVFT24bcMKpBLBaYVu32TxU5nh +SnUgnZUP5NbcA/FZGOhHibJXWpS2qdgXKxdJ5XbLwVaZOjex/2kskZGT4d9Mozd2 +TaGf+G0eHdP67Pv0RR0Tbc/3WeUiJ3IrhvNXuzDtJE3cfVa7o7P4NHmJweDyAmH3 +pvwPuxwXC65B2Xy9J6P9LjrRk5Sxcx0ki69bIImtt2dmefU6xqaWM/5TkshGsRGR +xpl/j8nWZjEgQRCHLQzWwa80mMpkg/sTV9HB8Dx6jKXB/ZUhoHHBk2dxEuqPiApp +GWSZI1b7rCoucL5mxAyE7+WL85MB+GqQk2dLsmijtWKP6T+MejteD+eMuMZ87zf9 +dOLITzNy4ZQ5bb0Sr74MTnB8G2+NszKTc0QWbej09+CVgI+WXTik9KveCjCHk9hN +AHFiRSdLOkKEW39lt2c0Ui2cFmuqqNh7o0JMcccMyj6D5KbvtwEwXlGjefVwaaZB +RA+GsCyRxj3qrg+E +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIIDrzCCApegAwIBAgIQCDvgVpBCRrGhdWrJWZHHSjANBgkqhkiG9w0BAQUFADBh +MQswCQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3 +d3cuZGlnaWNlcnQuY29tMSAwHgYDVQQDExdEaWdpQ2VydCBHbG9iYWwgUm9vdCBD +QTAeFw0wNjExMTAwMDAwMDBaFw0zMTExMTAwMDAwMDBaMGExCzAJBgNVBAYTAlVT +MRUwEwYDVQQKEwxEaWdpQ2VydCBJbmMxGTAXBgNVBAsTEHd3dy5kaWdpY2VydC5j +b20xIDAeBgNVBAMTF0RpZ2lDZXJ0IEdsb2JhbCBSb290IENBMIIBIjANBgkqhkiG +9w0BAQEFAAOCAQ8AMIIBCgKCAQEA4jvhEXLeqKTTo1eqUKKPC3eQyaKl7hLOllsB +CSDMAZOnTjC3U/dDxGkAV53ijSLdhwZAAIEJzs4bg7/fzTtxRuLWZscFs3YnFo97 +nh6Vfe63SKMI2tavegw5BmV/Sl0fvBf4q77uKNd0f3p4mVmFaG5cIzJLv07A6Fpt +43C/dxC//AH2hdmoRBBYMql1GNXRor5H4idq9Joz+EkIYIvUX7Q6hL+hqkpMfT7P +T19sdl6gSzeRntwi5m3OFBqOasv+zbMUZBfHWymeMr/y7vrTC0LUq7dBMtoM1O/4 +gdW7jVg/tRvoSSiicNoxBN33shbyTApOB6jtSj1etX+jkMOvJwIDAQABo2MwYTAO +BgNVHQ8BAf8EBAMCAYYwDwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQUA95QNVbR +TLtm8KPiGxvDl7I90VUwHwYDVR0jBBgwFoAUA95QNVbRTLtm8KPiGxvDl7I90VUw +DQYJKoZIhvcNAQEFBQADggEBAMucN6pIExIK+t1EnE9SsPTfrgT1eXkIoyQY/Esr +hMAtudXH/vTBH1jLuG2cenTnmCmrEbXjcKChzUyImZOMkXDiqw8cvpOp/2PV5Adg +06O/nVsJ8dWO41P0jmP6P6fbtGbfYmbW0W5BjfIttep3Sp+dWOIrWcBAI+0tKIJF +PnlUkiaY4IBIqDfv8NZ5YBberOgOzW6sRBc4L0na4UU+Krk2U886UAb3LujEV0ls +YSEY1QSteDwsOoBrp+uvFRTp2InBuThs4pFsiv9kuXclVzDAGySj4dzp30d8tbQk +CAUw7C29C79Fv1C5qfPrmAESrciIxpg0X40KPMbp1ZWVbd4= +-----END CERTIFICATE----- diff --git a/crates/alien-bindings/src/providers/postgres/cloud.rs b/crates/alien-bindings/src/providers/postgres/cloud.rs new file mode 100644 index 000000000..766fe16ed --- /dev/null +++ b/crates/alien-bindings/src/providers/postgres/cloud.rs @@ -0,0 +1,28 @@ +//! Surface shared by the three cloud Postgres backends (Aurora, Cloud SQL, Flexible +//! Server). +//! +//! Everything here is cloud-only, so the module is gated once at its `mod` declaration +//! rather than attribute-by-attribute: a local-only build has no secret store to read +//! and no cloud handle to hand back. + +use crate::error::{ErrorData, Result}; +use alien_core::bindings::BindingValue; +use alien_error::Context; + +/// Extracts a cloud variant's secret locator (ARN / name / URI) as a concrete string. +/// +/// `field` is the camelCase binding field name, so the error names the key the user +/// would actually look for in `ALIEN__BINDING`. +pub(crate) fn resolve_secret_locator( + binding_name: &str, + field: &str, + locator: &BindingValue, +) -> Result { + locator + .clone() + .into_value(binding_name, field) + .context(ErrorData::config_invalid( + binding_name, + format!("Failed to extract '{field}' from Postgres binding"), + )) +} diff --git a/crates/alien-bindings/src/providers/postgres/cloud_sql.rs b/crates/alien-bindings/src/providers/postgres/cloud_sql.rs new file mode 100644 index 000000000..148ecb48e --- /dev/null +++ b/crates/alien-bindings/src/providers/postgres/cloud_sql.rs @@ -0,0 +1,305 @@ +//! GCP Cloud SQL Postgres provider. +//! +//! The binding carries the Secret Manager secret **name** of the connection password, +//! not the password. This provider reads the secret's latest version with the workload's +//! own identity (granted by the `postgres/data-access` permission set) and builds the +//! connection parameters. + +use crate::error::{ErrorData, Result}; +use crate::providers::postgres::{ + cloud::resolve_secret_locator, resolve_params, resolve_verify_ca_policy, + PostgresConnectionInput, +}; +use crate::traits::PostgresConnectionParams; +use alien_core::bindings::CloudSqlPostgresBinding; +use alien_error::{AlienError, Context, IntoAlienError}; +use alien_gcp_clients::secret_manager::SecretManagerApi; +use base64::{engine::general_purpose::STANDARD as base64_standard, Engine as _}; +use std::sync::Arc; + +/// Reads the password from Secret Manager and resolves the connection parameters. +/// +/// The workload dials the binding's `host` (the Private Service Connect consumer +/// endpoint) and verifies its per-instance CA (`sslmode=verify-ca`). Hostname +/// verification is not possible because the PSC consumer endpoint is an IP address +/// that is not present in the server certificate. +/// +/// Performs exactly one `accessSecretVersion`; a failure is returned to the caller, +/// which owns any retry policy. +pub(crate) async fn resolve( + binding_name: &str, + binding: &CloudSqlPostgresBinding, + secrets: Arc, +) -> Result { + let tls = resolve_verify_ca_policy(binding_name, &binding.server_ca_certificates)?; + let secret_name = resolve_secret_locator( + binding_name, + "passwordSecretName", + &binding.password_secret_name, + )?; + let password = read_password(binding_name, &secret_name, secrets.as_ref()).await?; + + resolve_params( + binding_name, + PostgresConnectionInput { + host: &binding.host, + port: &binding.port, + database: &binding.database, + username: &binding.username, + password: &password, + tls, + }, + ) +} + +/// Reads the raw password the GCP controller stored as the secret version's payload. +/// +/// The client scopes the name to the credential's project, so `{name}/versions/latest` +/// is the whole relative resource name this needs to pass. +async fn read_password( + binding_name: &str, + secret_name: &str, + secrets: &dyn SecretManagerApi, +) -> Result { + let read_failed = |reason: &str| ErrorData::PostgresSecretResolutionFailed { + binding_name: binding_name.to_string(), + secret: secret_name.to_string(), + reason: reason.to_string(), + }; + let invalid_value = |reason: &str| ErrorData::PostgresSecretValueInvalid { + binding_name: binding_name.to_string(), + secret: secret_name.to_string(), + reason: reason.to_string(), + }; + + let response = secrets + .access_secret_version(format!("{secret_name}/versions/latest")) + .await + .context(read_failed("Secret Manager accessSecretVersion failed"))?; + + // Secret Manager returns the payload base64-encoded. Retrying the same version cannot + // turn a missing, empty, or malformed payload into a valid password. + let encoded = response + .payload + .and_then(|payload| payload.data) + .ok_or_else(|| AlienError::new(invalid_value("secret version has no payload")))?; + + let decoded = base64_standard + .decode(&encoded) + .into_alien_error() + .context(invalid_value("payload is not valid base64"))?; + + if decoded.is_empty() { + return Err(AlienError::new(invalid_value( + "secret version payload is empty", + ))); + } + + String::from_utf8(decoded) + .into_alien_error() + .context(invalid_value("payload is not valid UTF-8")) +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::traits::SslMode; + use alien_core::bindings::BindingValue; + use alien_gcp_clients::secret_manager::{ + AccessSecretVersionResponse, MockSecretManagerApi, SecretPayload, + }; + + const SECRET_NAME: &str = "pg-credentials"; + const SERVER_CA: &str = + "-----BEGIN CERTIFICATE-----\ncloud-sql-instance-root\n-----END CERTIFICATE-----"; + + fn binding() -> CloudSqlPostgresBinding { + CloudSqlPostgresBinding { + host: "10.0.0.5".into(), + port: BindingValue::value(5432), + database: "app".into(), + username: "alien".into(), + server_ca_certificates: BindingValue::value(vec![SERVER_CA.to_string()]), + password_secret_name: SECRET_NAME.into(), + } + } + + fn response(payload: Option<&str>) -> AccessSecretVersionResponse { + AccessSecretVersionResponse { + name: Some(format!("projects/p/secrets/{SECRET_NAME}/versions/1")), + payload: payload.map(|data| SecretPayload { + data: Some(base64_standard.encode(data)), + }), + } + } + + /// The happy path: the latest version of the named secret is read, the binding's host + /// is dialed, TLS is required, and the base64 payload is decoded into the password. + /// The password contains every RFC 3986 sub-delim that `encodeURIComponent` leaves + /// literal, extending the encoding contract pinned in `local.rs` to this backend. + #[tokio::test] + async fn resolves_host_with_ca_verified_tls_from_latest_version() { + let mut secrets = MockSecretManagerApi::new(); + secrets + .expect_access_secret_version() + .times(1) + .withf(|name| name == "pg-credentials/versions/latest") + .returning(|_| Ok(response(Some("a!b*c'd(e)f@/")))); + + let params = resolve("db", &binding(), Arc::new(secrets)) + .await + .expect("cloud sql binding resolves"); + + assert_eq!(params.host, "10.0.0.5"); + assert_eq!(params.port, 5432); + assert_eq!(params.database, "app"); + assert_eq!(params.username, "alien"); + assert_eq!(params.password, "a!b*c'd(e)f@/"); + assert_eq!(params.sslmode(), SslMode::VerifyCa); + assert_eq!(params.ca_certificates(), &[SERVER_CA]); + assert_eq!( + params.connection_string(), + "postgres://alien:a%21b%2Ac%27d%28e%29f%40%2F@10.0.0.5:5432/app?sslmode=verify-ca" + ); + } + + #[tokio::test] + async fn missing_server_ca_fails_before_reading_the_password() { + for certificates in [ + BindingValue::value(Vec::new()), + BindingValue::Expression(serde_json::json!({ + "Fn::GetAtt": ["CloudSql", "ServerCaCertificates"] + })), + ] { + let mut secrets = MockSecretManagerApi::new(); + secrets.expect_access_secret_version().never(); + + let mut malformed = binding(); + malformed.server_ca_certificates = certificates; + + let error = resolve("db", &malformed, Arc::new(secrets)) + .await + .expect_err("Cloud SQL without concrete CA roots must fail closed"); + + assert_eq!(error.code, "BINDING_CONFIG_INVALID"); + assert!(!error.retryable); + } + } + + /// A failed secret read is upstream/transient — it must stay retryable so an + /// automated retry layer can recover, and it must never be reported as bad user config. + #[tokio::test] + async fn failed_secret_read_is_retryable() { + let mut secrets = MockSecretManagerApi::new(); + secrets + .expect_access_secret_version() + .times(1) + .returning(|_| { + Err(AlienError::new( + alien_client_core::ErrorData::RemoteServiceUnavailable { + message: "secretmanager.googleapis.com is unavailable".to_string(), + }, + )) + }); + + let error = resolve("db", &binding(), Arc::new(secrets)) + .await + .expect_err("a failed secret read must not resolve a connection"); + + assert_eq!(error.code, "POSTGRES_SECRET_RESOLUTION_FAILED"); + assert!(error.retryable, "an upstream read failure is retryable"); + assert!( + error.to_string().contains(SECRET_NAME), + "the error must name the secret locator so operators can find it, got: {error}" + ); + } + + /// An empty or missing payload must fail rather than silently connect with no + /// password. An empty base64 payload decodes to zero bytes, which is easy to let + /// through — assert both shapes. + #[tokio::test] + async fn empty_payload_fails_resolution() { + for stored in [None, Some("")] { + let mut secrets = MockSecretManagerApi::new(); + secrets + .expect_access_secret_version() + .times(1) + .returning(move |_| Ok(response(stored))); + + let error = resolve("db", &binding(), Arc::new(secrets)) + .await + .expect_err("an empty payload must not resolve a connection"); + + assert_eq!(error.code, "POSTGRES_SECRET_VALUE_INVALID"); + assert!(!error.retryable); + assert!(!error.internal); + } + } + + /// Decode failures are permanent but retain their third-party source error. + #[tokio::test] + async fn malformed_payload_is_non_retryable_and_preserves_source() { + let responses = [ + ( + "not valid base64".to_string(), + "payload is not valid base64", + ), + (base64_standard.encode([0xff]), "payload is not valid UTF-8"), + ]; + + for (encoded, expected_reason) in responses { + let mut secrets = MockSecretManagerApi::new(); + secrets + .expect_access_secret_version() + .times(1) + .returning(move |_| { + Ok(AccessSecretVersionResponse { + name: Some(format!("projects/p/secrets/{SECRET_NAME}/versions/1")), + payload: Some(SecretPayload { + data: Some(encoded.clone()), + }), + }) + }); + + let error = resolve("db", &binding(), Arc::new(secrets)) + .await + .expect_err("a malformed payload must not resolve a connection"); + + assert_eq!(error.code, "POSTGRES_SECRET_VALUE_INVALID"); + assert!(!error.retryable); + assert!(!error.internal); + assert!( + error.to_string().contains(expected_reason), + "error must identify the malformed payload: {error}" + ); + assert!( + std::error::Error::source(&error).is_some(), + "the decode error must be retained as the source" + ); + } + } + + /// A malformed binding is user-fixable configuration: it must not be retryable, and + /// it must fail before any secret read is attempted. + #[tokio::test] + async fn unresolved_secret_name_is_non_retryable_config_error() { + let mut secrets = MockSecretManagerApi::new(); + secrets.expect_access_secret_version().never(); + + let mut malformed = binding(); + malformed.password_secret_name = BindingValue::Expression(serde_json::json!({ + "Fn::GetAtt": ["PgSecret", "Name"] + })); + + let error = resolve("db", &malformed, Arc::new(secrets)) + .await + .expect_err("an unresolved secret name must not resolve a connection"); + + assert_eq!(error.code, "BINDING_CONFIG_INVALID"); + assert!(!error.retryable, "bad binding config is user-fixable"); + assert!( + error.to_string().contains("ALIEN_DB_BINDING"), + "the error must name the env var the user would edit, got: {error}" + ); + } +} diff --git a/crates/alien-bindings/src/providers/postgres/flexible_server.rs b/crates/alien-bindings/src/providers/postgres/flexible_server.rs new file mode 100644 index 000000000..2be337b1b --- /dev/null +++ b/crates/alien-bindings/src/providers/postgres/flexible_server.rs @@ -0,0 +1,412 @@ +//! Azure Database for PostgreSQL — Flexible Server provider. +//! +//! The binding carries the Key Vault secret **URI** of the connection password, not the +//! password. This provider reads it with the workload's own identity (granted by the +//! `postgres/data-access` permission set) and builds the connection parameters. + +use crate::error::{ErrorData, Result}; +use crate::providers::postgres::{ + azure_postgres_tls_policy, cloud::resolve_secret_locator, resolve_params, + PostgresConnectionInput, +}; +use crate::traits::PostgresConnectionParams; +use alien_azure_clients::keyvault::KeyVaultSecretsApi; +use alien_core::bindings::FlexibleServerPostgresBinding; +use alien_error::{AlienError, Context, IntoAlienError}; +use std::sync::Arc; +use url::Url; + +const PUBLIC_KEY_VAULT_DNS_SUFFIX: &str = ".vault.azure.net"; + +/// The parts of a Key Vault secret URI needed to read it. +#[derive(Debug, PartialEq, Eq)] +struct SecretUri { + /// Vault base URL, e.g. `https://my-vault.vault.azure.net`. + vault_base_url: String, + /// Secret name. + name: String, + /// Explicit version, or `None` for the latest. + version: Option, +} + +/// Reads the password from Key Vault and resolves the connection parameters. +/// +/// The workload dials the binding's `host` (the private DNS FQDN fronting the +/// server's Private Endpoint) and verifies the Azure roots and hostname +/// (`sslmode=verify-full`). +/// +/// Performs exactly one `getSecret`; a failure is returned to the caller, which owns +/// any retry policy. +pub(crate) async fn resolve( + binding_name: &str, + binding: &FlexibleServerPostgresBinding, + secrets: Arc, +) -> Result { + let secret_uri = resolve_secret_locator( + binding_name, + "passwordSecretUri", + &binding.password_secret_uri, + )?; + let parsed = parse_secret_uri(binding_name, &secret_uri)?; + let password = read_password(binding_name, &secret_uri, &parsed, secrets.as_ref()).await?; + + resolve_params( + binding_name, + PostgresConnectionInput { + host: &binding.host, + port: &binding.port, + database: &binding.database, + username: &binding.username, + password: &password, + tls: azure_postgres_tls_policy(), + }, + ) +} + +/// Splits a Key Vault secret URI into the vault URL, secret name, and optional version. +/// +/// The URI must identify a public-cloud Azure Key Vault over HTTPS, and the path must be +/// exactly `/secrets/[/]`; a missing version means "latest". Rejecting +/// every other origin prevents a binding-controlled URI from receiving a Key Vault token. +fn parse_secret_uri(binding_name: &str, secret_uri: &str) -> Result { + let malformed = |reason: &str| { + ErrorData::config_invalid( + binding_name, + format!("Postgres password secret URI '{secret_uri}' {reason}"), + ) + }; + + let url = Url::parse(secret_uri) + .into_alien_error() + .context(malformed("is not a valid URL"))?; + + if url.scheme() != "https" { + return Err(AlienError::new(malformed("must use HTTPS"))); + } + if !url.username().is_empty() || url.password().is_some() { + return Err(AlienError::new(malformed( + "must not contain user information", + ))); + } + if url.port().is_some() { + return Err(AlienError::new(malformed("must not specify a port"))); + } + if url.query().is_some() || url.fragment().is_some() { + return Err(AlienError::new(malformed( + "must not contain a query or fragment", + ))); + } + + let host = url.host_str().ok_or_else(|| { + AlienError::new(malformed( + "must use a public Azure Key Vault host ending in '.vault.azure.net'", + )) + })?; + let vault_name = host + .strip_suffix(PUBLIC_KEY_VAULT_DNS_SUFFIX) + .filter(|name| valid_vault_name(name)) + .ok_or_else(|| { + AlienError::new(malformed( + "must use a public Azure Key Vault host ending in '.vault.azure.net'", + )) + })?; + + let segments: Vec<&str> = url + .path_segments() + .map(Iterator::collect) + .unwrap_or_default(); + + if segments.first() != Some(&"secrets") + || !(2..=3).contains(&segments.len()) + || !segments[1..].iter().all(|segment| valid_secret_id(segment)) + { + return Err(AlienError::new(malformed( + "is not a '/secrets/[/]' Key Vault URL", + ))); + } + + Ok(SecretUri { + vault_base_url: format!("https://{vault_name}{PUBLIC_KEY_VAULT_DNS_SUFFIX}"), + name: segments[1].to_string(), + version: segments.get(2).map(|version| (*version).to_string()), + }) +} + +/// Public Azure Key Vault names are a single 3–24 character DNS label. +fn valid_vault_name(name: &str) -> bool { + (3..=24).contains(&name.len()) + && name.as_bytes().first().is_some_and(u8::is_ascii_alphabetic) + && name + .as_bytes() + .last() + .is_some_and(u8::is_ascii_alphanumeric) + && name + .bytes() + .all(|byte| byte.is_ascii_alphanumeric() || byte == b'-') + && !name.contains("--") +} + +/// Key Vault secret names and version identifiers are path-safe ASCII identifiers. +fn valid_secret_id(value: &str) -> bool { + !value.is_empty() + && value + .bytes() + .all(|byte| byte.is_ascii_alphanumeric() || byte == b'-') +} + +/// Reads the raw password the Azure controller stored as a Key Vault secret. +async fn read_password( + binding_name: &str, + secret_uri: &str, + parsed: &SecretUri, + secrets: &dyn KeyVaultSecretsApi, +) -> Result { + let read_failed = |reason: &str| ErrorData::PostgresSecretResolutionFailed { + binding_name: binding_name.to_string(), + secret: secret_uri.to_string(), + reason: reason.to_string(), + }; + let invalid_value = |reason: &str| ErrorData::PostgresSecretValueInvalid { + binding_name: binding_name.to_string(), + secret: secret_uri.to_string(), + reason: reason.to_string(), + }; + + let bundle = secrets + .get_secret( + parsed.vault_base_url.clone(), + parsed.name.clone(), + parsed.version.clone(), + ) + .await + .context(read_failed("Key Vault getSecret failed"))?; + + // Retrying the same secret version cannot turn a missing value into a password. + bundle + .value + .filter(|password| !password.is_empty()) + .ok_or_else(|| AlienError::new(invalid_value("secret value is missing or empty"))) +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::traits::SslMode; + use alien_azure_clients::keyvault::MockKeyVaultSecretsApi; + use alien_azure_clients::models::secrets::SecretBundle; + use alien_core::bindings::BindingValue; + + const VAULT: &str = "https://alien-vault.vault.azure.net"; + const SECRET_URI: &str = "https://alien-vault.vault.azure.net/secrets/pg-password"; + + fn binding(secret_uri: &str) -> FlexibleServerPostgresBinding { + FlexibleServerPostgresBinding { + host: "pg.privatelink.postgres.database.azure.com".into(), + port: BindingValue::value(5432), + database: "app".into(), + username: "alien".into(), + password_secret_uri: secret_uri.into(), + } + } + + fn bundle(value: Option<&str>) -> SecretBundle { + SecretBundle { + value: value.map(str::to_string), + ..SecretBundle::default() + } + } + + /// The happy path: the URI splits into vault + name (no version, i.e. latest), the + /// binding's host is dialed, and TLS is required. The password contains every RFC 3986 + /// sub-delim that `encodeURIComponent` leaves literal, extending the encoding contract + /// pinned in `local.rs` to this backend. + #[tokio::test] + async fn resolves_host_with_verified_tls_from_secret_uri() { + let mut secrets = MockKeyVaultSecretsApi::new(); + secrets + .expect_get_secret() + .times(1) + .withf(|vault, name, version| { + vault == VAULT && name == "pg-password" && version.is_none() + }) + .returning(|_, _, _| Ok(bundle(Some("a!b*c'd(e)f@/")))); + + let params = resolve("db", &binding(SECRET_URI), Arc::new(secrets)) + .await + .expect("flexible server binding resolves"); + + assert_eq!(params.host, "pg.privatelink.postgres.database.azure.com"); + assert_eq!(params.port, 5432); + assert_eq!(params.database, "app"); + assert_eq!(params.username, "alien"); + assert_eq!(params.password, "a!b*c'd(e)f@/"); + assert_eq!(params.sslmode(), SslMode::VerifyFull); + assert_eq!(params.ca_certificates().len(), 1); + assert_eq!( + params.ca_certificates()[0] + .matches("-----BEGIN CERTIFICATE-----") + .count(), + 3, + "the embedded set must carry only Azure's recommended roots" + ); + assert_eq!( + params.connection_string(), + "postgres://alien:a%21b%2Ac%27d%28e%29f%40%2F@\ + pg.privatelink.postgres.database.azure.com:5432/app?sslmode=verify-full" + ); + } + + /// A URI that pins a version must read exactly that version, not "latest". + #[tokio::test] + async fn versioned_secret_uri_reads_that_version() { + let mut secrets = MockKeyVaultSecretsApi::new(); + secrets + .expect_get_secret() + .times(1) + .withf(|vault, name, version| { + vault == VAULT && name == "pg-password" && version.as_deref() == Some("abc123") + }) + .returning(|_, _, _| Ok(bundle(Some("pw")))); + + let params = resolve( + "db", + &binding(&format!("{SECRET_URI}/abc123")), + Arc::new(secrets), + ) + .await + .expect("versioned secret URI resolves"); + + assert_eq!(params.password, "pw"); + } + + /// A failed secret read is upstream/transient — it must stay retryable so an + /// automated retry layer can recover, and it must never be reported as bad user config. + #[tokio::test] + async fn failed_secret_read_is_retryable() { + let mut secrets = MockKeyVaultSecretsApi::new(); + secrets.expect_get_secret().times(1).returning(|_, _, _| { + Err(AlienError::new(alien_client_core::ErrorData::Timeout { + message: "Key Vault request timed out".to_string(), + })) + }); + + let error = resolve("db", &binding(SECRET_URI), Arc::new(secrets)) + .await + .expect_err("a failed secret read must not resolve a connection"); + + assert_eq!(error.code, "POSTGRES_SECRET_RESOLUTION_FAILED"); + assert!(error.retryable, "an upstream read failure is retryable"); + assert!( + error.to_string().contains(SECRET_URI), + "the error must name the secret locator so operators can find it, got: {error}" + ); + } + + /// An empty stored secret must fail rather than silently connect with no password. + #[tokio::test] + async fn empty_secret_value_fails_resolution() { + for stored in [None, Some("")] { + let mut secrets = MockKeyVaultSecretsApi::new(); + secrets + .expect_get_secret() + .times(1) + .returning(move |_, _, _| Ok(bundle(stored))); + + let error = resolve("db", &binding(SECRET_URI), Arc::new(secrets)) + .await + .expect_err("an empty secret must not resolve a connection"); + + assert_eq!(error.code, "POSTGRES_SECRET_VALUE_INVALID"); + assert!(!error.retryable); + assert!(!error.internal); + } + } + + /// A URI this provider cannot interpret is user-fixable configuration: not retryable, + /// and it must fail before any secret read is attempted. Covers every rejected shape. + #[tokio::test] + async fn malformed_secret_uri_is_non_retryable_config_error() { + let cases = [ + ("not-a-url", "unparseable"), + ( + "http://alien-vault.vault.azure.net/secrets/pg-password", + "insecure scheme", + ), + ("https://example.com/secrets/pg-password", "arbitrary host"), + ( + "https://alien-vault.vault.azure.net.example.com/secrets/pg-password", + "suffix lookalike", + ), + ( + "https://nested.alien-vault.vault.azure.net/secrets/pg-password", + "nested subdomain", + ), + ( + "https://user:password@alien-vault.vault.azure.net/secrets/pg-password", + "user information", + ), + ( + "https://alien-vault.vault.azure.net:8443/secrets/pg-password", + "custom port", + ), + ( + "https://alien-vault.vault.azure.net/secrets/pg-password?api-version=7.4", + "query", + ), + ( + "https://alien-vault.vault.azure.net/secrets/pg-password#fragment", + "fragment", + ), + ( + "https://alien-vault.vault.azure.net/keys/pg-password", + "wrong collection", + ), + ("https://alien-vault.vault.azure.net/secrets", "no name"), + ( + "https://alien-vault.vault.azure.net/secrets/pg-password/v1/extra", + "trailing segments", + ), + ( + "https://alien-vault.vault.azure.net/secrets/pg%2Fpassword", + "encoded path separator", + ), + ]; + + for (uri, why) in cases { + let mut secrets = MockKeyVaultSecretsApi::new(); + secrets.expect_get_secret().never(); + + let error = match resolve("db", &binding(uri), Arc::new(secrets)).await { + Ok(_) => panic!("'{uri}' ({why}) must be rejected"), + Err(error) => error, + }; + + assert_eq!(error.code, "BINDING_CONFIG_INVALID", "for '{uri}' ({why})"); + assert!(!error.retryable, "for '{uri}' ({why})"); + } + } + + /// An unresolved binding value (a template expression that never got substituted) is + /// also user-fixable configuration and must not reach the secret store. + #[tokio::test] + async fn unresolved_secret_uri_is_non_retryable_config_error() { + let mut secrets = MockKeyVaultSecretsApi::new(); + secrets.expect_get_secret().never(); + + let mut malformed = binding(SECRET_URI); + malformed.password_secret_uri = BindingValue::Expression(serde_json::json!({ + "Fn::GetAtt": ["PgSecret", "Uri"] + })); + + let error = resolve("db", &malformed, Arc::new(secrets)) + .await + .expect_err("an unresolved secret URI must not resolve a connection"); + + assert_eq!(error.code, "BINDING_CONFIG_INVALID"); + assert!(!error.retryable, "bad binding config is user-fixable"); + assert!( + error.to_string().contains("ALIEN_DB_BINDING"), + "the error must name the env var the user would edit, got: {error}" + ); + } +} diff --git a/crates/alien-bindings/src/providers/postgres/local.rs b/crates/alien-bindings/src/providers/postgres/local.rs index c78b5330d..a336953d5 100644 --- a/crates/alien-bindings/src/providers/postgres/local.rs +++ b/crates/alien-bindings/src/providers/postgres/local.rs @@ -1,130 +1,88 @@ -use crate::error::{binding_env_var, ErrorData, Result}; -use crate::traits::{Binding, Postgres, PostgresConnectionParams, SslMode}; -use alien_core::bindings::{BindingValue, PostgresBinding}; -use alien_error::{AlienError, Context}; +use crate::error::{ErrorData, Result}; +use crate::providers::postgres::{resolve_params, PostgresConnectionInput, ResolvedPostgres}; +use crate::traits::PostgresTlsPolicy; +use alien_core::bindings::{ExternalPostgresSslMode, PostgresBinding}; +use alien_error::AlienError; -/// A resolved Postgres binding. Holds connection details only — it never opens or -/// owns a server process. -#[derive(Debug)] -pub struct LocalPostgres { - params: PostgresConnectionParams, -} - -impl LocalPostgres { - pub fn new(params: PostgresConnectionParams) -> Self { - Self { params } - } +/// Backwards-compatible name for a resolved inline-password Postgres binding. +/// +/// All Postgres backends now use the same resolved handle internally, but local +/// platform consumers historically constructed this public type directly. +pub type LocalPostgres = ResolvedPostgres; - /// Resolves connection parameters from a binding. Handles the Local and External (BYO) - /// variants. Cloud variants (Aurora / Cloud SQL / Flexible Server) carry only a *reference* to - /// the connection password in a cloud secret store; the workload SDK - /// (`packages/sdk/src/bindings/postgres.ts`) resolves it in-process with the workload's own - /// identity. This Rust provider intentionally does not read cloud secrets, so it rejects cloud - /// bindings by design rather than half-resolving them. +impl ResolvedPostgres { + /// Resolves connection parameters from a binding that carries its password inline: + /// the Local (developer) and External (BYO / Kubernetes) variants. + /// + /// The cloud variants carry a secret *locator* instead and need an async read against + /// that cloud's secret store, so they have their own providers (`aurora`, `cloud_sql`, + /// `flexible_server`) that `BindingsProvider::load_postgres` dispatches to. pub fn from_binding(binding_name: &str, binding: &PostgresBinding) -> Result { let params = match binding { PostgresBinding::Local(b) => resolve_params( binding_name, - &b.host, - &b.port, - &b.database, - &b.username, - &b.password, - SslMode::Disable, - )?, - PostgresBinding::External(b) => resolve_params( - binding_name, - &b.host, - &b.port, - &b.database, - &b.username, - &b.password, - SslMode::Prefer, + PostgresConnectionInput { + host: &b.host, + port: &b.port, + database: &b.database, + username: &b.username, + // Inline password is already a concrete `String` (the type forbids an + // unresolved ref). + password: &b.password, + tls: PostgresTlsPolicy::disabled(), + }, )?, - // Cloud variants are resolved by the workload SDK (see the method doc), not here. Listed - // explicitly rather than via a catch-all so a future `PostgresBinding` variant forces a - // compile error to handle it. Name the backend so a reader of a later cloud plan can tell - // which variant was rejected. + PostgresBinding::External(b) => { + let tls = match b.ssl_mode { + ExternalPostgresSslMode::VerifyFull => { + PostgresTlsPolicy::verify_full_with_system_roots() + } + ExternalPostgresSslMode::Disable => PostgresTlsPolicy::disabled(), + }; + resolve_params( + binding_name, + PostgresConnectionInput { + host: &b.host, + port: &b.port, + database: &b.database, + username: &b.username, + password: &b.password, + tls, + }, + )? + } + // Listed explicitly rather than via a catch-all so a future `PostgresBinding` + // variant forces a compile error to route it somewhere. Reaching this arm means + // `load_postgres` dispatched a cloud binding to the wrong provider — a bug here, + // not bad user configuration. PostgresBinding::Aurora(_) | PostgresBinding::CloudSql(_) | PostgresBinding::FlexibleServer(_) => { - let backend = match binding { - PostgresBinding::Aurora(_) => "Aurora (AWS)", - PostgresBinding::CloudSql(_) => "Cloud SQL (GCP)", - PostgresBinding::FlexibleServer(_) => "Azure Flexible Server", - _ => "cloud", - }; - return Err(AlienError::new(ErrorData::BindingConfigInvalid { - env_var: binding_env_var(binding_name), - binding_name: binding_name.to_string(), - reason: format!( - "{backend} Postgres bindings are resolved in-process by the workload SDK, \ - not this Rust provider" - ), - })); + return Err(AlienError::new(ErrorData::config_invalid( + binding_name, + "Cloud Postgres bindings carry a password secret locator and must be \ + resolved by their own cloud provider, not the inline-password provider", + ))); } }; Ok(Self::new(params)) } } -#[allow(clippy::too_many_arguments)] -fn resolve_params( - binding_name: &str, - host: &BindingValue, - port: &BindingValue, - database: &BindingValue, - username: &BindingValue, - password: &str, - sslmode: SslMode, -) -> Result { - let invalid = |field: &str| ErrorData::BindingConfigInvalid { - env_var: binding_env_var(binding_name), - binding_name: binding_name.to_string(), - reason: format!("Failed to extract '{}' from Postgres binding", field), - }; - Ok(PostgresConnectionParams { - host: host - .clone() - .into_value(binding_name, "host") - .context(invalid("host"))?, - port: port - .clone() - .into_value(binding_name, "port") - .context(invalid("port"))?, - database: database - .clone() - .into_value(binding_name, "database") - .context(invalid("database"))?, - username: username - .clone() - .into_value(binding_name, "username") - .context(invalid("username"))?, - // Inline password is already a concrete `String` (the type forbids an unresolved ref). - password: password.to_string(), - sslmode, - }) -} - -impl Binding for LocalPostgres {} - -impl Postgres for LocalPostgres { - fn connection_params(&self) -> PostgresConnectionParams { - self.params.clone() - } -} - #[cfg(test)] mod tests { use super::*; + use crate::traits::{Postgres, SslMode}; + use alien_core::bindings::BindingValue; #[test] fn local_binding_resolves_to_disable_sslmode_connection_string() { let binding = PostgresBinding::local("127.0.0.1", 6543, "db", "alien", "p@ss/word"); - let pg = LocalPostgres::from_binding("db", &binding).expect("local binding resolves"); + let pg = ResolvedPostgres::from_binding("db", &binding).expect("local binding resolves"); let params = pg.connection_params(); assert_eq!(params.host, "127.0.0.1"); assert_eq!(params.port, 6543); + assert_eq!(params.sslmode(), SslMode::Disable); // password is percent-encoded; sslmode=disable for Local (plain TCP). assert_eq!( pg.connection_string(), @@ -132,22 +90,58 @@ mod tests { ); } + #[test] + fn external_binding_defaults_to_verify_full_sslmode() { + let binding = PostgresBinding::external("db.internal", 5432, "app", "alien", "p@ss/word"); + let pg = ResolvedPostgres::from_binding("db", &binding).expect("external binding resolves"); + + assert_eq!(pg.connection_params().sslmode(), SslMode::VerifyFull); + assert_eq!( + pg.connection_string(), + "postgres://alien:p%40ss%2Fword@db.internal:5432/app?sslmode=verify-full" + ); + } + + #[test] + fn external_binding_allows_explicit_plaintext_opt_out() { + let binding: PostgresBinding = serde_json::from_value(serde_json::json!({ + "service": "external", + "host": "db.internal", + "port": 5432, + "database": "app", + "username": "alien", + "password": "secret", + "sslMode": "disable", + })) + .expect("external plaintext binding deserializes"); + let pg = ResolvedPostgres::from_binding("db", &binding).expect("external binding resolves"); + + assert_eq!(pg.connection_params().sslmode(), SslMode::Disable); + assert_eq!( + pg.connection_string(), + "postgres://alien:secret@db.internal:5432/app?sslmode=disable" + ); + } + // The connection string must percent-encode the RFC 3986 sub-delims ! * ' ( ) that JS's - // encodeURIComponent leaves literal, so the Rust resolver and the TS SDK resolver - // (packages/sdk/.../postgres.ts `encodeUserinfo`) produce byte-identical URLs for any - // generated password. This pins the shared encoding contract on the Rust side. + // encodeURIComponent leaves literal, so any resolver that reimplements this (in any + // language) produces byte-identical URLs for any generated password. This pins the + // encoding contract; `crates/alien-bindings/src/traits.rs::encode_userinfo` is the + // single implementation every backend shares. #[test] fn connection_string_percent_encodes_rfc3986_sub_delims() { let binding = PostgresBinding::local("h", 5432, "db", "alien", "a!b*c'd(e)f"); - let pg = LocalPostgres::from_binding("db", &binding).expect("local binding resolves"); + let pg = ResolvedPostgres::from_binding("db", &binding).expect("local binding resolves"); assert_eq!( pg.connection_string(), "postgres://alien:a%21b%2Ac%27d%28e%29f@h:5432/db?sslmode=disable" ); } + /// A cloud binding routed here is a dispatch bug, not user error, but it must still + /// fail loudly rather than resolve without a password. #[test] - fn cloud_binding_resolution_is_rejected_in_this_build() { + fn cloud_binding_is_rejected_by_the_inline_password_provider() { let binding = PostgresBinding::Aurora(alien_core::bindings::AuroraPostgresBinding { cluster_endpoint: "cluster.rds.amazonaws.com".into(), port: BindingValue::value(5432), @@ -155,6 +149,10 @@ mod tests { username: "alien".into(), password_secret_arn: "arn:aws:secretsmanager:...".into(), }); - assert!(LocalPostgres::from_binding("db", &binding).is_err()); + + let error = ResolvedPostgres::from_binding("db", &binding) + .expect_err("cloud bindings must not resolve without their secret"); + + assert_eq!(error.code, "BINDING_CONFIG_INVALID"); } } diff --git a/crates/alien-bindings/src/providers/postgres/mod.rs b/crates/alien-bindings/src/providers/postgres/mod.rs index 0e28df9ae..4b254d402 100644 --- a/crates/alien-bindings/src/providers/postgres/mod.rs +++ b/crates/alien-bindings/src/providers/postgres/mod.rs @@ -1,7 +1,321 @@ //! Postgres binding providers. //! //! Postgres is connection-only: the provider resolves connection details and the -//! application connects with its own driver. There is no gRPC service (by design), -//! so the cloud providers that resolve a secret in-process land with each cloud plan. +//! application connects with its own driver. There is no gRPC service (by design). +//! +//! `Local` and `External` carry the password inline. The three cloud variants carry only +//! a *pointer* to the password in that cloud's secret store (Secrets Manager ARN / +//! Secret Manager name / Key Vault secret URI) — the password never flows through the +//! control plane and never sits in a plaintext environment variable. Each cloud provider +//! reads that pointer with the workload's own identity, which is exactly what the +//! `postgres/data-access` permission set grants. +//! +//! Resolution happens up front, when the binding is loaded, so +//! [`crate::traits::Postgres`] stays synchronous and one handle can be read repeatedly +//! without another secret read. A cloud handle therefore holds the password that was +//! current when it was created; `BindingsProvider::load_postgres` deliberately does not +//! cache it, so loading the binding again re-reads the secret and picks up a rotation. +//! (The secret-store *client* is cached, so re-reading does not rebuild a connection +//! pool; [`runtime::PostgresRuntime`] owns both policies.) +#[cfg(feature = "aws")] +pub(crate) mod aurora; +#[cfg(any(feature = "aws", feature = "gcp", feature = "azure"))] +pub(crate) mod cloud; +#[cfg(feature = "gcp")] +pub(crate) mod cloud_sql; +#[cfg(feature = "azure")] +pub(crate) mod flexible_server; pub mod local; +pub(crate) mod runtime; + +use crate::error::{ErrorData, Result}; +#[cfg(any(feature = "gcp", test))] +use crate::traits::SslMode; +use crate::traits::{Binding, Postgres, PostgresConnectionParams, PostgresTlsPolicy}; +use alien_core::bindings::BindingValue; +#[cfg(feature = "gcp")] +use alien_error::AlienError; +use alien_error::Context; +#[cfg(any(feature = "aws", feature = "azure"))] +use std::sync::OnceLock; + +/// Official Amazon RDS roots for every commercial region. +/// +/// Source: . +/// AWS uses region-specific roots for each supported CA algorithm, so the global +/// set is intentionally larger than a conventional public-root bundle. +#[cfg(feature = "aws")] +pub(crate) const AWS_RDS_CA_CERTIFICATES: &[&str] = &[include_str!("ca/aws-rds-global-roots.pem")]; + +/// Roots currently recommended by Azure Database for PostgreSQL. +/// +/// Root rotation is handled by updating this embedded set and releasing the SDK. +/// Intermediate and server certificates must never be added. +#[cfg(feature = "azure")] +pub(crate) const AZURE_POSTGRES_CA_CERTIFICATES: &[&str] = + &[include_str!("ca/azure-postgres-roots.pem")]; + +/// A Postgres handle whose cloud-specific work has already been completed. +/// +/// Local, external, and cloud bindings differ only while resolving their connection +/// details. They all expose the same immutable handle afterwards. +#[derive(Debug)] +pub struct ResolvedPostgres { + params: PostgresConnectionParams, +} + +impl ResolvedPostgres { + pub fn new(params: PostgresConnectionParams) -> Self { + Self { params } + } +} + +impl Binding for ResolvedPostgres {} + +impl Postgres for ResolvedPostgres { + fn connection_params(&self) -> &PostgresConnectionParams { + &self.params + } +} + +/// Concrete inputs shared by every Postgres backend after password and TLS resolution. +pub(crate) struct PostgresConnectionInput<'a> { + pub(crate) host: &'a BindingValue, + pub(crate) port: &'a BindingValue, + pub(crate) database: &'a BindingValue, + pub(crate) username: &'a BindingValue, + pub(crate) password: &'a str, + pub(crate) tls: PostgresTlsPolicy, +} + +/// Combines a binding's concrete connection fields with an already-resolved `password`. +/// +/// Every field arrives as a [`BindingValue`], so an unresolved template expression or +/// `SecretRef` is a user-fixable configuration problem (`BINDING_CONFIG_INVALID`, not +/// retryable) rather than a runtime failure. +/// +/// `host` is whichever field the backend dials: the cluster endpoint for Aurora, the +/// host for every other backend. +pub(crate) fn resolve_params( + binding_name: &str, + input: PostgresConnectionInput<'_>, +) -> Result { + let invalid = |field: &str| ErrorData::BindingConfigInvalid { + env_var: crate::error::binding_env_var(binding_name), + binding_name: binding_name.to_string(), + reason: format!("Failed to extract '{}' from Postgres binding", field), + }; + + Ok(PostgresConnectionParams::new( + input + .host + .clone() + .into_value(binding_name, "host") + .context(invalid("host"))?, + input + .port + .clone() + .into_value(binding_name, "port") + .context(invalid("port"))?, + input + .database + .clone() + .into_value(binding_name, "database") + .context(invalid("database"))?, + input + .username + .clone() + .into_value(binding_name, "username") + .context(invalid("username"))?, + input.password.to_string(), + input.tls, + )) +} + +/// Resolves and validates the per-instance CA roots carried by a Cloud SQL binding. +#[cfg(feature = "gcp")] +pub(crate) fn resolve_verify_ca_policy( + binding_name: &str, + certificates: &BindingValue>, +) -> Result { + let certificates = certificates + .clone() + .into_value(binding_name, "serverCaCertificates") + .context(ErrorData::config_invalid( + binding_name, + "Failed to extract 'serverCaCertificates' from Postgres binding", + ))?; + verified_tls_policy( + binding_name, + SslMode::VerifyCa, + PostgresTlsPolicy::verify_ca(certificates), + ) +} + +#[cfg(feature = "gcp")] +fn verified_tls_policy( + binding_name: &str, + sslmode: SslMode, + policy: std::result::Result, +) -> Result { + policy.map_err(|_| { + AlienError::new(ErrorData::config_invalid( + binding_name, + format!( + "Postgres sslmode '{}' requires one or more non-empty PEM server CA certificates", + sslmode.as_str() + ), + )) + }) +} + +/// Cached Aurora TLS policy. Its large embedded root bundle is parsed and allocated once, +/// then shared by all resolved handles. +#[cfg(feature = "aws")] +pub(crate) fn aws_rds_tls_policy() -> PostgresTlsPolicy { + static POLICY: OnceLock = OnceLock::new(); + POLICY + .get_or_init(|| { + PostgresTlsPolicy::verify_full( + AWS_RDS_CA_CERTIFICATES + .iter() + .map(|certificate| (*certificate).to_string()) + .collect(), + ) + .expect("the embedded AWS RDS root bundle must contain valid PEM certificates") + }) + .clone() +} + +/// Cached Flexible Server TLS policy. Embedded roots are parsed and allocated once, +/// then shared by all resolved handles. +#[cfg(feature = "azure")] +pub(crate) fn azure_postgres_tls_policy() -> PostgresTlsPolicy { + static POLICY: OnceLock = OnceLock::new(); + POLICY + .get_or_init(|| { + PostgresTlsPolicy::verify_full( + AZURE_POSTGRES_CA_CERTIFICATES + .iter() + .map(|certificate| (*certificate).to_string()) + .collect(), + ) + .expect("the embedded Azure Postgres roots must contain valid PEM certificates") + }) + .clone() +} + +#[cfg(test)] +mod tests { + use super::*; + + /// An unresolved `SecretRef` in a connection field must fail as user-fixable config, + /// not silently produce a half-resolved connection. + #[test] + fn unresolved_secret_ref_field_is_binding_config_invalid() { + let error = resolve_params( + "db", + PostgresConnectionInput { + host: &BindingValue::SecretRef { + secret_ref: alien_core::bindings::SecretReference { + name: "pg-credentials".to_string(), + key: "host".to_string(), + }, + }, + port: &BindingValue::value(5432), + database: &"db".into(), + username: &"alien".into(), + password: "pw", + tls: PostgresTlsPolicy::verify_full(vec![pem("root")]).unwrap(), + }, + ) + .expect_err("an unresolved SecretRef host must not resolve"); + + assert_eq!(error.code, "BINDING_CONFIG_INVALID"); + assert!(!error.retryable, "bad binding config is user-fixable"); + assert!( + error.to_string().contains("host"), + "the error must name the offending field, got: {error}" + ); + } + + /// The redacting `Debug` on `PostgresConnectionParams` is the only thing keeping a + /// resolved cloud password out of logs and panic output; every handle derives its own + /// `Debug` from it, so pin it here so a derive can never quietly replace it. + #[test] + fn debug_output_never_contains_the_password() { + let params = resolve_params( + "db", + PostgresConnectionInput { + host: &"h".into(), + port: &BindingValue::value(5432), + database: &"db".into(), + username: &"alien".into(), + password: "super-secret-password", + tls: PostgresTlsPolicy::verify_full(vec![pem("root")]).unwrap(), + }, + ) + .expect("concrete fields resolve"); + + let rendered = format!("{params:?}"); + assert!( + !rendered.contains("super-secret-password"), + "password leaked into Debug output: {rendered}" + ); + assert!( + !rendered.contains("BEGIN CERTIFICATE"), + "certificate bundle expanded into Debug output: {rendered}" + ); + assert!(rendered.contains(""), "got: {rendered}"); + } + + #[test] + fn verify_ca_requires_valid_ca_certificates() { + for ca_certificates in [ + Vec::new(), + vec!["".to_string()], + vec!["not a certificate".to_string()], + ] { + let error = verified_tls_policy( + "db", + SslMode::VerifyCa, + PostgresTlsPolicy::verify_ca(ca_certificates), + ) + .expect_err("verified TLS without a PEM root must fail closed"); + + assert_eq!(error.code, "BINDING_CONFIG_INVALID"); + assert!(!error.retryable); + } + } + + #[test] + fn verify_full_can_use_the_system_trust_store() { + let params = resolve_params( + "db", + PostgresConnectionInput { + host: &"db.example.com".into(), + port: &BindingValue::value(5432), + database: &"db".into(), + username: &"alien".into(), + password: "pw", + tls: PostgresTlsPolicy::verify_full_with_system_roots(), + }, + ) + .expect("BYO verify-full can rely on the runtime trust store"); + + assert!(params.ca_certificates().is_empty()); + assert_eq!(params.sslmode(), SslMode::VerifyFull); + } + + #[test] + fn tls_policy_cannot_pair_plaintext_with_roots_or_verify_ca_without_roots() { + assert!(PostgresTlsPolicy::verify_ca(Vec::new()).is_err()); + assert!(PostgresTlsPolicy::disabled().ca_certificates().is_empty()); + assert_eq!(PostgresTlsPolicy::disabled().sslmode(), SslMode::Disable); + } + + fn pem(body: &str) -> String { + format!("-----BEGIN CERTIFICATE-----\n{body}\n-----END CERTIFICATE-----") + } +} diff --git a/crates/alien-bindings/src/providers/postgres/runtime.rs b/crates/alien-bindings/src/providers/postgres/runtime.rs new file mode 100644 index 000000000..b2a994a69 --- /dev/null +++ b/crates/alien-bindings/src/providers/postgres/runtime.rs @@ -0,0 +1,379 @@ +//! Runtime state for resolving Postgres bindings. +//! +//! Inline-password bindings are immutable and their resolved handles are cached by +//! binding name. Cloud bindings are resolved on every load so password rotation is +//! observable, while their typed secret-store clients are initialized once and reused. + +use crate::{ + error::{ErrorData, Result}, + traits::Postgres, +}; +#[cfg(any(feature = "aws", feature = "gcp", feature = "azure"))] +use alien_core::Platform; +use alien_core::{bindings::PostgresBinding, ClientConfig}; +use alien_error::AlienError; +#[cfg(feature = "aws")] +use alien_error::Context; +use std::{collections::HashMap, fmt, sync::Arc}; +#[cfg(any(feature = "aws", feature = "gcp", feature = "azure"))] +use tokio::sync::OnceCell; +use tokio::sync::RwLock; + +use super::ResolvedPostgres; + +#[cfg(feature = "aws")] +use super::aurora; +#[cfg(feature = "gcp")] +use super::cloud_sql; +#[cfg(feature = "azure")] +use super::flexible_server; +#[cfg(feature = "aws")] +use alien_aws_clients::secrets_manager::{SecretsManagerApi, SecretsManagerClient}; +#[cfg(feature = "azure")] +use alien_azure_clients::{ + keyvault::{AzureKeyVaultSecretsClient, KeyVaultSecretsApi}, + AzureTokenCache, +}; +#[cfg(feature = "gcp")] +use alien_gcp_clients::secret_manager::{SecretManagerApi, SecretManagerClient}; + +pub(crate) struct PostgresRuntime { + #[cfg(any(feature = "aws", feature = "gcp", feature = "azure"))] + client_config: ClientConfig, + inline: RwLock>>, + #[cfg(feature = "aws")] + aws_secrets: OnceCell>, + #[cfg(feature = "gcp")] + gcp_secrets: OnceCell>, + #[cfg(feature = "azure")] + azure_secrets: OnceCell>, +} + +impl fmt::Debug for PostgresRuntime { + fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result { + let mut debug = formatter.debug_struct("PostgresRuntime"); + debug.field("client_config", &""); + debug.field("inline", &""); + #[cfg(feature = "aws")] + debug.field("aws_secrets_initialized", &self.aws_secrets.initialized()); + #[cfg(feature = "gcp")] + debug.field("gcp_secrets_initialized", &self.gcp_secrets.initialized()); + #[cfg(feature = "azure")] + debug.field( + "azure_secrets_initialized", + &self.azure_secrets.initialized(), + ); + debug.finish() + } +} + +impl PostgresRuntime { + pub(crate) fn new(client_config: ClientConfig) -> Self { + #[cfg(not(any(feature = "aws", feature = "gcp", feature = "azure")))] + let _ = client_config; + + Self { + #[cfg(any(feature = "aws", feature = "gcp", feature = "azure"))] + client_config, + inline: RwLock::new(HashMap::new()), + #[cfg(feature = "aws")] + aws_secrets: OnceCell::new(), + #[cfg(feature = "gcp")] + gcp_secrets: OnceCell::new(), + #[cfg(feature = "azure")] + azure_secrets: OnceCell::new(), + } + } + + pub(crate) async fn load( + &self, + binding_name: &str, + binding: &PostgresBinding, + ) -> Result> { + match binding { + PostgresBinding::Local(_) | PostgresBinding::External(_) => { + self.load_inline(binding_name, binding).await + } + #[cfg(feature = "aws")] + PostgresBinding::Aurora(config) => { + let params = + aurora::resolve(binding_name, config, self.aws_secrets().await?).await?; + Ok(Arc::new(ResolvedPostgres::new(params))) + } + #[cfg(not(feature = "aws"))] + PostgresBinding::Aurora(_) => Err(feature_not_enabled("aws")), + #[cfg(feature = "gcp")] + PostgresBinding::CloudSql(config) => { + let params = + cloud_sql::resolve(binding_name, config, self.gcp_secrets().await?).await?; + Ok(Arc::new(ResolvedPostgres::new(params))) + } + #[cfg(not(feature = "gcp"))] + PostgresBinding::CloudSql(_) => Err(feature_not_enabled("gcp")), + #[cfg(feature = "azure")] + PostgresBinding::FlexibleServer(config) => { + let params = + flexible_server::resolve(binding_name, config, self.azure_secrets().await?) + .await?; + Ok(Arc::new(ResolvedPostgres::new(params))) + } + #[cfg(not(feature = "azure"))] + PostgresBinding::FlexibleServer(_) => Err(feature_not_enabled("azure")), + } + } + + async fn load_inline( + &self, + binding_name: &str, + binding: &PostgresBinding, + ) -> Result> { + if let Some(postgres) = self.inline.read().await.get(binding_name).cloned() { + return Ok(postgres); + } + + let resolved: Arc = + Arc::new(ResolvedPostgres::from_binding(binding_name, binding)?); + let mut cache = self.inline.write().await; + Ok(cache + .entry(binding_name.to_string()) + .or_insert(resolved) + .clone()) + } + + #[cfg(feature = "aws")] + async fn aws_secrets(&self) -> Result> { + let client = self + .aws_secrets + .get_or_try_init(|| async { + let config = self.client_config.aws_config().ok_or_else(|| { + AlienError::new(ErrorData::ClientConfigInvalid { + platform: Platform::Aws, + message: "AWS config not available".to_string(), + }) + })?; + let credentials = + alien_aws_clients::AwsCredentialProvider::from_config(config.clone()) + .await + .context(ErrorData::ClientConfigInvalid { + platform: Platform::Aws, + message: "Failed to create AWS credential provider".to_string(), + })?; + Ok::, AlienError>(Arc::new( + SecretsManagerClient::new( + crate::http_client::create_http_client(), + credentials, + ), + )) + }) + .await?; + Ok(client.clone()) + } + + #[cfg(feature = "gcp")] + async fn gcp_secrets(&self) -> Result> { + let client = self + .gcp_secrets + .get_or_try_init(|| async { + let config = self.client_config.gcp_config().ok_or_else(|| { + AlienError::new(ErrorData::ClientConfigInvalid { + platform: Platform::Gcp, + message: "GCP config not available".to_string(), + }) + })?; + Ok::, AlienError>(Arc::new( + SecretManagerClient::new( + crate::http_client::create_http_client(), + config.clone(), + ), + )) + }) + .await?; + Ok(client.clone()) + } + + #[cfg(feature = "azure")] + async fn azure_secrets(&self) -> Result> { + let client = self + .azure_secrets + .get_or_try_init(|| async { + let config = self.client_config.azure_config().ok_or_else(|| { + AlienError::new(ErrorData::ClientConfigInvalid { + platform: Platform::Azure, + message: "Azure config not available".to_string(), + }) + })?; + Ok::, AlienError>(Arc::new( + AzureKeyVaultSecretsClient::new( + crate::http_client::create_http_client(), + AzureTokenCache::new(config.clone()), + ), + )) + }) + .await?; + Ok(client.clone()) + } +} + +#[cfg(any(not(feature = "aws"), not(feature = "gcp"), not(feature = "azure")))] +fn feature_not_enabled(feature: &str) -> AlienError { + AlienError::new(ErrorData::FeatureNotEnabled { + feature: feature.to_string(), + }) +} + +#[cfg(test)] +mod tests { + use super::*; + + #[tokio::test] + async fn inline_password_binding_is_served_from_the_cache() { + let runtime = PostgresRuntime::new(ClientConfig::Test); + let binding = PostgresBinding::Local(alien_core::bindings::LocalPostgresBinding { + host: "127.0.0.1".into(), + port: alien_core::bindings::BindingValue::value(6543), + database: "app".into(), + username: "alien".into(), + password: "inline-pw".into(), + }); + + let first = runtime.load("db", &binding).await.expect("first load"); + let second = runtime.load("db", &binding).await.expect("second load"); + + assert!( + Arc::ptr_eq(&first, &second), + "an inline-password binding should return the cached handle" + ); + assert_eq!(first.connection_params().password, "inline-pw"); + } + + #[cfg(feature = "aws")] + mod aws_tests { + use super::*; + use alien_core::{AwsClientConfig, AwsCredentials, AwsServiceOverrides}; + use axum::{ + extract::{ConnectInfo, State}, + routing::post, + Json, Router, + }; + use std::{net::SocketAddr, sync::Mutex}; + + const SECRET_ARN: &str = "arn:aws:secretsmanager:us-east-1:000000000000:secret:pg-AbCdEf"; + type ReadLog = Arc>>; + + async fn get_secret_value( + State(reads): State, + ConnectInfo(peer): ConnectInfo, + ) -> Json { + let nth = { + let mut reads = reads.lock().expect("read log"); + reads.push(peer); + reads.len() + }; + Json(serde_json::json!({ + "ARN": SECRET_ARN, + "Name": "pg", + "SecretString": format!("password-v{nth}"), + })) + } + + async fn spawn_secrets_manager() -> (SocketAddr, ReadLog) { + let reads: ReadLog = Arc::new(Mutex::new(Vec::new())); + let app = Router::new() + .route("/", post(get_secret_value)) + .with_state(reads.clone()); + let listener = tokio::net::TcpListener::bind(SocketAddr::from(([127, 0, 0, 1], 0))) + .await + .expect("bind"); + let addr = listener.local_addr().expect("addr"); + tokio::spawn(async move { + axum::serve( + listener, + app.into_make_service_with_connect_info::(), + ) + .await + .expect("serve"); + }); + (addr, reads) + } + + fn runtime(addr: SocketAddr) -> PostgresRuntime { + PostgresRuntime::new(ClientConfig::Aws(Box::new(AwsClientConfig { + account_id: "000000000000".to_string(), + region: "us-east-1".to_string(), + credentials: AwsCredentials::AccessKeys { + access_key_id: "AKIAIOSFODNN7EXAMPLE".to_string(), + secret_access_key: "wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY".to_string(), + session_token: None, + }, + service_overrides: Some(AwsServiceOverrides { + endpoints: HashMap::from([( + "secretsmanager".to_string(), + format!("http://{addr}"), + )]), + }), + }))) + } + + fn binding() -> PostgresBinding { + serde_json::from_value(serde_json::json!({ + "service": "aurora", + "clusterEndpoint": "cluster.cluster-abc.us-east-1.rds.amazonaws.com", + "port": 5432, + "database": "app", + "username": "alien", + "passwordSecretArn": SECRET_ARN, + })) + .expect("binding parses") + } + + #[tokio::test] + async fn cloud_binding_rereads_its_secret_on_every_load() { + let (addr, reads) = spawn_secrets_manager().await; + let runtime = runtime(addr); + let binding = binding(); + + let first = runtime.load("db", &binding).await.expect("first load"); + let second = runtime.load("db", &binding).await.expect("second load"); + + assert_eq!(reads.lock().expect("read log").len(), 2); + assert_eq!(first.connection_params().password, "password-v1"); + assert_eq!(second.connection_params().password, "password-v2"); + } + + #[tokio::test] + async fn concurrent_first_loads_initialize_one_secret_store_client() { + let (addr, _reads) = spawn_secrets_manager().await; + let runtime = Arc::new(runtime(addr)); + let binding = Arc::new(binding()); + + let (first, second) = tokio::join!( + runtime.load("db", binding.as_ref()), + runtime.load("db", binding.as_ref()), + ); + + first.expect("first load"); + second.expect("second load"); + assert!( + runtime.aws_secrets.get().is_some(), + "the typed client should be initialized once and shared" + ); + } + + #[tokio::test] + async fn cloud_binding_reuses_its_secret_store_client_across_loads() { + let (addr, reads) = spawn_secrets_manager().await; + let runtime = runtime(addr); + let binding = binding(); + + runtime.load("db", &binding).await.expect("first load"); + runtime.load("db", &binding).await.expect("second load"); + + let peers = reads.lock().expect("read log").clone(); + assert_eq!(peers.len(), 2); + assert_eq!( + peers[0], peers[1], + "both reads should reuse one client connection pool" + ); + } + } +} diff --git a/crates/alien-bindings/src/traits.rs b/crates/alien-bindings/src/traits.rs index 9770f820d..374b13c5d 100644 --- a/crates/alien-bindings/src/traits.rs +++ b/crates/alien-bindings/src/traits.rs @@ -412,27 +412,130 @@ pub trait Vault: Binding { async fn list_secrets(&self) -> Result>; } -/// TLS mode used when building a Postgres connection string. +/// TLS policy used when building a Postgres connection string. #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub enum SslMode { - /// Plain TCP, no TLS (Local). + /// Plain TCP, no TLS (Local or an explicit BYO opt-out). Disable, - /// Use TLS if the server offers it, otherwise plain (BYO / External default). - Prefer, - /// Require TLS (cloud). - Require, + /// Require TLS and verify the server certificate against a trusted CA. + VerifyCa, + /// Require TLS and verify both the trusted CA chain and the dialed hostname + /// (BYO / External default, Aurora, and Flexible Server). + VerifyFull, } impl SslMode { - fn as_str(self) -> &'static str { + /// The `sslmode` query-parameter value, and the wire form embedders (the napi addon) + /// hand to their own callers. + pub fn as_str(self) -> &'static str { match self { SslMode::Disable => "disable", - SslMode::Prefer => "prefer", - SslMode::Require => "require", + SslMode::VerifyCa => "verify-ca", + SslMode::VerifyFull => "verify-full", } } } +/// Invalid PEM roots supplied to a verified Postgres TLS policy. +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub struct InvalidPostgresCaCertificates; + +impl std::fmt::Display for InvalidPostgresCaCertificates { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.write_str("expected one or more non-empty PEM certificates") + } +} + +impl std::error::Error for InvalidPostgresCaCertificates {} + +/// A complete Postgres TLS policy. +/// +/// The fields are private so callers cannot pair plaintext with CA roots or construct +/// `verify-ca` without a root. Cloning the policy is cheap: certificate bundles are +/// reference-counted and copied only when crossing an FFI boundary. +#[derive(Clone, PartialEq, Eq)] +pub struct PostgresTlsPolicy { + sslmode: SslMode, + ca_certificates: Arc<[String]>, +} + +impl std::fmt::Debug for PostgresTlsPolicy { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.debug_struct("PostgresTlsPolicy") + .field("sslmode", &self.sslmode) + .field("ca_certificate_count", &self.ca_certificates.len()) + .finish() + } +} + +impl PostgresTlsPolicy { + /// Plain TCP with no certificate roots. + pub fn disabled() -> Self { + Self { + sslmode: SslMode::Disable, + ca_certificates: Arc::default(), + } + } + + /// TLS with CA verification but no hostname verification. + /// + /// This mode requires at least one valid PEM root. + pub fn verify_ca( + ca_certificates: Vec, + ) -> std::result::Result { + Self::verified(SslMode::VerifyCa, ca_certificates, true) + } + + /// TLS with CA and hostname verification. + /// + /// An empty root set intentionally selects the runtime's system trust store. + pub fn verify_full( + ca_certificates: Vec, + ) -> std::result::Result { + Self::verified(SslMode::VerifyFull, ca_certificates, false) + } + + /// TLS with CA and hostname verification using the runtime's system trust store. + pub fn verify_full_with_system_roots() -> Self { + Self { + sslmode: SslMode::VerifyFull, + ca_certificates: Arc::default(), + } + } + + fn verified( + sslmode: SslMode, + ca_certificates: Vec, + require_roots: bool, + ) -> std::result::Result { + if (require_roots && ca_certificates.is_empty()) + || ca_certificates.iter().any(|certificate| { + let certificate = certificate.trim(); + !certificate.starts_with("-----BEGIN CERTIFICATE-----") + || !certificate.ends_with("-----END CERTIFICATE-----") + }) + { + return Err(InvalidPostgresCaCertificates); + } + + Ok(Self { + sslmode, + ca_certificates: ca_certificates.into(), + }) + } + + /// The libpq-compatible `sslmode` represented by this complete policy. + pub fn sslmode(&self) -> SslMode { + self.sslmode + } + + /// PEM-encoded roots, or an empty slice when this policy uses no roots or the + /// runtime's system trust store. + pub fn ca_certificates(&self) -> &[String] { + &self.ca_certificates + } +} + /// Resolved connection details for a Postgres database. #[derive(Clone)] pub struct PostgresConnectionParams { @@ -441,7 +544,7 @@ pub struct PostgresConnectionParams { pub database: String, pub username: String, pub password: String, - pub sslmode: SslMode, + pub tls: PostgresTlsPolicy, } // Hand-written Debug so the resolved password never reaches logs, error chains, or panic output @@ -454,12 +557,42 @@ impl std::fmt::Debug for PostgresConnectionParams { .field("database", &self.database) .field("username", &self.username) .field("password", &"") - .field("sslmode", &self.sslmode) + .field("tls", &self.tls) .finish() } } impl PostgresConnectionParams { + /// Creates resolved connection details from a complete, internally consistent TLS + /// policy. + pub fn new( + host: String, + port: u16, + database: String, + username: String, + password: String, + tls: PostgresTlsPolicy, + ) -> Self { + Self { + host, + port, + database, + username, + password, + tls, + } + } + + /// The libpq-compatible TLS mode used by this connection. + pub fn sslmode(&self) -> SslMode { + self.tls.sslmode() + } + + /// PEM-encoded root CA certificates. + pub fn ca_certificates(&self) -> &[String] { + self.tls.ca_certificates() + } + /// Builds a `postgres://` URL. Username and password are percent-encoded so a /// generated password containing URL-special characters can never corrupt it. pub fn connection_string(&self) -> String { @@ -472,7 +605,7 @@ impl PostgresConnectionParams { // Encode the database path segment so this URL stays byte-identical to the TS // resolver's `encodeUserinfo` (the same RFC 3986 unreserved-set encoding). encode_userinfo(&self.database), - self.sslmode.as_str(), + self.sslmode().as_str(), ) } } @@ -496,7 +629,7 @@ fn encode_userinfo(value: &str) -> String { /// speaks the same wire protocol, so the binding returns connection details and the /// application uses its own driver. pub trait Postgres: Binding { - fn connection_params(&self) -> PostgresConnectionParams; + fn connection_params(&self) -> &PostgresConnectionParams; /// `postgres://` connection string, derived from `connection_params` and never stored. fn connection_string(&self) -> String { @@ -808,9 +941,10 @@ pub trait BindingsProviderApi: Send + Sync + std::fmt::Debug { /// Given a binding identifier, builds a Postgres implementation. /// - /// Only the **local** (developer) backend is supported here. Cloud backends (Aurora, CloudSQL, - /// Azure Flexible Server) are resolved by the TypeScript SDK only; a Rust worker that requests one - /// gets a runtime error from the local resolver, with no compile-time gate. + /// Every backend is resolved here. Local and External carry their password inline; + /// Aurora, Cloud SQL, and Azure Flexible Server carry only a locator for it and read + /// the value from that cloud's secret store during this call, using the workload's own + /// identity. Resolution happens once per load, so the returned handle is synchronous. async fn load_postgres(&self, binding_name: &str) -> Result>; /// Given a binding identifier, builds a Queue implementation. diff --git a/crates/alien-core/src/bindings/mod.rs b/crates/alien-core/src/bindings/mod.rs index 793102de1..67d8b0889 100644 --- a/crates/alien-core/src/bindings/mod.rs +++ b/crates/alien-core/src/bindings/mod.rs @@ -43,7 +43,7 @@ pub use kv::{ }; pub use postgres::{ AuroraPostgresBinding, CloudSqlPostgresBinding, ExternalPostgresBinding, - FlexibleServerPostgresBinding, LocalPostgresBinding, PostgresBinding, + ExternalPostgresSslMode, FlexibleServerPostgresBinding, LocalPostgresBinding, PostgresBinding, }; pub use queue::{ LocalQueueBinding, PubSubQueueBinding, QueueBinding, ServiceBusQueueBinding, SqsQueueBinding, diff --git a/crates/alien-core/src/bindings/postgres.rs b/crates/alien-core/src/bindings/postgres.rs index 98e3fbfb0..fea036acf 100644 --- a/crates/alien-core/src/bindings/postgres.rs +++ b/crates/alien-core/src/bindings/postgres.rs @@ -55,6 +55,9 @@ pub struct CloudSqlPostgresBinding { pub port: BindingValue, pub database: BindingValue, pub username: BindingValue, + /// Per-instance Cloud SQL server CA certificates, including every root accepted + /// during a CA rotation. + pub server_ca_certificates: BindingValue>, /// Secret Manager secret name of the connection password; resolved at load time. pub password_secret_name: BindingValue, } @@ -73,6 +76,22 @@ pub struct FlexibleServerPostgresBinding { pub password_secret_uri: BindingValue, } +/// TLS policy for an operator-provided / BYO Postgres database. +/// +/// Unlike libpq's ambiguous `prefer` mode, both choices map exactly to the +/// connection settings exposed by every supported SDK. +#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Serialize, Deserialize)] +#[cfg_attr(feature = "openapi", derive(utoipa::ToSchema))] +#[cfg_attr(feature = "jsonschema", derive(schemars::JsonSchema))] +#[serde(rename_all = "kebab-case")] +pub enum ExternalPostgresSslMode { + /// Require TLS and verify the server certificate and hostname. + #[default] + VerifyFull, + /// Connect over plaintext. Intended only for explicitly configured legacy servers. + Disable, +} + /// Operator-provided / BYO database binding. // No derived `Debug` — inline `password` would print cleartext; see the redacting impl below. #[derive(Clone, PartialEq, Eq, Serialize, Deserialize)] @@ -88,6 +107,9 @@ pub struct ExternalPostgresBinding { /// materializes the Kubernetes secret into the pod env. The cloud variants carry a secret /// locator instead. pub password: String, + /// Explicit TLS policy. Missing legacy configuration defaults to verified TLS. + #[serde(default)] + pub ssl_mode: ExternalPostgresSslMode, } /// Local embedded Postgres binding. @@ -114,6 +136,7 @@ impl std::fmt::Debug for ExternalPostgresBinding { .field("database", &self.database) .field("username", &self.username) .field("password", &"") + .field("ssl_mode", &self.ssl_mode) .finish() } } @@ -162,6 +185,7 @@ impl PostgresBinding { database: database.into(), username: username.into(), password: password.into(), + ssl_mode: ExternalPostgresSslMode::default(), }) } } @@ -182,12 +206,55 @@ mod tests { #[test] fn external_binding_uses_external_tag() { let binding = PostgresBinding::external("db.internal", 5432, "app", "alien", "secret"); - let json = serde_json::to_string(&binding).unwrap(); + let json = serde_json::to_string(&binding).expect("external binding serializes"); assert!(json.contains(r#""service":"external""#)); - let deserialized: PostgresBinding = serde_json::from_str(&json).unwrap(); + assert!(json.contains(r#""sslMode":"verify-full""#)); + let deserialized: PostgresBinding = + serde_json::from_str(&json).expect("external binding deserializes"); assert_eq!(binding, deserialized); } + #[test] + fn external_binding_without_ssl_mode_defaults_to_verified_tls() { + let json = r#"{ + "service": "external", + "host": "db.internal", + "port": 5432, + "database": "app", + "username": "alien", + "password": "secret" + }"#; + + let binding: PostgresBinding = + serde_json::from_str(json).expect("legacy external binding deserializes"); + + let PostgresBinding::External(binding) = binding else { + panic!("expected external Postgres binding"); + }; + assert_eq!(binding.ssl_mode, ExternalPostgresSslMode::VerifyFull); + } + + #[test] + fn external_binding_accepts_explicit_plaintext_opt_out() { + let json = r#"{ + "service": "external", + "host": "db.internal", + "port": 5432, + "database": "app", + "username": "alien", + "password": "secret", + "sslMode": "disable" + }"#; + + let binding: PostgresBinding = + serde_json::from_str(json).expect("plaintext external binding deserializes"); + + let PostgresBinding::External(binding) = binding else { + panic!("expected external Postgres binding"); + }; + assert_eq!(binding.ssl_mode, ExternalPostgresSslMode::Disable); + } + #[test] fn cloud_variants_keep_hyphenated_tags() { let aurora = PostgresBinding::Aurora(AuroraPostgresBinding { @@ -206,6 +273,7 @@ mod tests { port: BindingValue::value(5432), database: "db".into(), username: "alien".into(), + server_ca_certificates: BindingValue::value(vec!["cloud-sql-root".to_string()]), password_secret_name: "pg-credentials".into(), }); assert!(serde_json::to_string(&cloud_sql) diff --git a/packages/bindings/PACKAGE_LAYOUT.md b/packages/bindings/PACKAGE_LAYOUT.md index ede8dcfce..a02965016 100644 --- a/packages/bindings/PACKAGE_LAYOUT.md +++ b/packages/bindings/PACKAGE_LAYOUT.md @@ -9,12 +9,14 @@ `@alienplatform/bindings` is the public, direct bindings package for TypeScript: a thin wrapper over a napi-rs native addon. The Rust crate `alien-bindings` is the single provider implementation (S3/GCS/Blob, DynamoDB/Firestore/Table Storage, -SQS/Pub-Sub/Service Bus, the vaults, the local providers, and their credential -chains). The TypeScript layer carries only types, the binding factories, and error -mapping; every storage/kv/queue/vault/container operation runs the Rust provider in-process. - -The package exposes five app-facing binding kinds — **storage**, **kv**, -**queue**, **vault**, and linked **container** discovery. It has no JS cloud SDK dependencies and no provider logic of +SQS/Pub-Sub/Service Bus, the vaults, the Postgres backends, the local providers, and +their credential chains). The TypeScript layer carries only types, the binding +factories, and error mapping; every storage/kv/queue/vault/container/postgres operation +runs the Rust provider in-process. + +The package exposes six app-facing binding kinds — **storage**, **kv**, +**queue**, **vault**, linked **container** discovery, and **postgres** connection +details. It has no JS cloud SDK dependencies and no provider logic of its own. ## Public surface — all exports from `"."` @@ -26,12 +28,17 @@ its own. | `queue` | function | `queue(name: string): Queue` | Factory. | | `vault` | function | `vault(name: string): Vault` | Factory. | | `container` | function | `container(name: string): Container` | Lazy, read-only linked-service discovery. | +| `postgres` | function | `postgres(name: string): Postgres` | Connection-only. Resolving a managed cloud backend reads its password from that cloud's secret store with the workload's own identity. External/BYO bindings default to certificate- and hostname-verified TLS; plaintext requires an explicit `sslMode: "disable"` policy. | | `Storage` | type | resource handle | Instance type returned by `storage()`. Operation method signatures mirror the Rust `alien-bindings` storage handle. | | `Kv` | type | resource handle | Instance type returned by `kv()`. Method signatures mirror the Rust handle. | | `Queue` | type | resource handle | Instance type returned by `queue()`. Method signatures mirror the Rust handle. | | `Container` | type | resource handle | `getInternalUrl()` and nullable `getPublicUrl()`. | | `Vault` | type | resource handle | Instance type returned by `vault()`. Method signatures mirror the Rust handle. | +| `Postgres` | type | resource handle | Instance type returned by `postgres()`: `connection()`. | +| `PostgresConnection`, `PostgresSslMode` | type | connection details | The URL, the driver `ssl` value, and the same details as individual fields. | | `BindingNotConfiguredError` | error | `defineError({ code: "BINDING_NOT_CONFIGURED", context: { binding, envVar } })` | Thrown on the first operation against an unconfigured binding. `binding` is the binding name; `envVar` is `ALIEN__BINDING`. | +| `UnknownPostgresSslModeError` | error | `defineError({ code: "UNKNOWN_POSTGRES_SSLMODE", context: { sslmode, expected } })` | Thrown when the native addon reports an `sslmode` this wrapper does not know — a version skew between the two halves of the package. | +| `InvalidPostgresTlsConfigError` | error | `defineError({ code: "INVALID_POSTGRES_TLS_CONFIG", context: { sslmode, reason } })` | Thrown when the native addon reports a verified mode without usable CA roots or another contradictory TLS configuration. | | shared error primitives | re-export | `AlienError`, `defineError` (from `@alienplatform/core`) | Re-exported so consumers handle bindings errors without a direct `@alienplatform/core` import. | ### Intentionally not exposed @@ -40,7 +47,6 @@ The non-app binding kinds are deliberately absent from this package and must not added: - worker invoke -- container - build - artifact-registry - service-account diff --git a/packages/bindings/README.md b/packages/bindings/README.md index 1d41382dd..155b192b5 100644 --- a/packages/bindings/README.md +++ b/packages/bindings/README.md @@ -1,6 +1,7 @@ # `@alienplatform/bindings` -Direct TypeScript bindings for Alien storage, kv, queue, vault, and linked-container discovery over an +Direct TypeScript bindings for Alien storage, kv, queue, vault, linked-container +discovery, and Postgres over an in-process [napi-rs](https://napi.rs) addon. The addon itself lives in the Rust crate `crates/alien-bindings-node`; this package is the published JavaScript wrapper that loads it. @@ -20,6 +21,43 @@ const publicUrl = await database.getPublicUrl() // string | null Use the internal URL for calls between resources in the same deployment. Use the public URL only when the caller is outside that private network. +## Postgres + +Postgres is connection-only: every backend speaks the same wire protocol, so the +binding hands back connection details and your app connects with its own driver. +Resolving a managed cloud backend (Aurora, Cloud SQL, Flexible Server) reads the +password from that cloud's secret store using the workload's own identity — the +binding itself only ever carries a locator for it. + +```ts +import { Client } from "pg" +import { postgres } from "@alienplatform/bindings" + +const conn = await postgres("my-db").connection() + +// Field style: node-postgres parses a URL's sslmode and would override `ssl`, so +// pass the fields when you need `conn.ssl` to take effect. +const client = new Client({ + host: conn.host, + port: conn.port, + database: conn.database, + user: conn.username, + password: conn.password, + ssl: conn.ssl, +}) +await client.connect() +``` + +`conn.connectionString` is the same details as a `postgres://` URL, with the +credentials percent-encoded, for drivers that take one. + +External (BYO) Postgres bindings use verified TLS by default. Their `sslMode` +policy is deliberately limited to `"verify-full"` and `"disable"` because +node-postgres cannot represent libpq's opportunistic `prefer` fallback without +owning the connection attempt. Use `"disable"` only as an explicit compatibility +opt-out for a plaintext-only server; missing legacy configuration defaults to +`"verify-full"`. + ## Native addon resolution The addon is loaded lazily on the first binding operation (never at import — the diff --git a/packages/bindings/package.json b/packages/bindings/package.json index 8bc6d4b7e..04454a7ab 100644 --- a/packages/bindings/package.json +++ b/packages/bindings/package.json @@ -30,10 +30,10 @@ "format-and-lint": "biome check .", "format-and-lint:fix": "biome check . --write" }, - "keywords": ["alien", "bindings", "storage", "kv", "queue", "vault", "napi"], + "keywords": ["alien", "bindings", "storage", "kv", "queue", "vault", "postgres", "napi"], "author": "Alien Software, Inc. ", "license": "FSL-1.1-Apache-2.0", - "description": "Direct TypeScript bindings for Alien storage, kv, queue, and vault over an in-process napi-rs addon", + "description": "Direct TypeScript bindings for Alien storage, kv, queue, vault, and postgres over an in-process napi-rs addon", "homepage": "https://alien.dev", "repository": { "type": "git", diff --git a/packages/bindings/scripts/run-bun-tests.mjs b/packages/bindings/scripts/run-bun-tests.mjs index cb5bcc8c7..8e4312b28 100644 --- a/packages/bindings/scripts/run-bun-tests.mjs +++ b/packages/bindings/scripts/run-bun-tests.mjs @@ -54,6 +54,25 @@ env[bindingEnvName("bun-container")] = JSON.stringify({ internalUrl: "http://database.internal:5432", publicUrl: "http://localhost:15432", }) +// Postgres is connection-only, so these need no data dir. The field values must match +// POSTGRES_FIXTURE in tests/helpers/local-binding-env.ts — tests/postgres.test.ts asserts +// the exact resolved URL and fails if the two drift. +const postgresFixture = { + host: "db.internal", + port: 5432, + database: "app", + username: "alien", + password: "a!b*c'd(e)f@/", +} +env[bindingEnvName("bun-postgres-local")] = JSON.stringify({ + service: "local-postgres", + ...postgresFixture, +}) +env[bindingEnvName("bun-postgres-external")] = JSON.stringify({ + service: "external", + ...postgresFixture, + sslMode: "verify-full", +}) const testFiles = readdirSync("tests") .filter(file => file.endsWith(".test.ts") && file !== "errors.test.ts") .map(file => `tests/${file}`) diff --git a/packages/bindings/src/__tests__/factories.test.ts b/packages/bindings/src/__tests__/factories.test.ts index 2b56ebda1..507e48163 100644 --- a/packages/bindings/src/__tests__/factories.test.ts +++ b/packages/bindings/src/__tests__/factories.test.ts @@ -6,6 +6,8 @@ import type { RawBindingsHandle, RawContainerHandle, RawKvHandle, + RawPostgresConnection, + RawPostgresHandle, RawQueueHandle, RawStorageHandle, RawVaultHandle, @@ -16,6 +18,43 @@ import type { * trivial resource handles, so factory behavior can be exercised without the * real `.node`. */ +/** + * A raw addon connection for `sslmode`, matching what the napi `PostgresHandle` + * returns: the connection string already carries the same mode. + */ +const TEST_CA = "-----BEGIN CERTIFICATE-----\ntest-root\n-----END CERTIFICATE-----" + +function rawConnection( + sslmode: unknown, + caCertificates: unknown = typeof sslmode === "string" && sslmode.startsWith("verify-") + ? [TEST_CA] + : [], +): RawPostgresConnection { + return { + connectionString: `postgres://alien:pw@db.internal:5432/app?sslmode=${sslmode}`, + host: "db.internal", + port: 5432, + database: "app", + username: "alien", + password: "pw", + sslmode, + caCertificates, + } +} + +/** Build an addon whose `postgres(name)` resolves to a handle returning `raw`. */ +function addonForPostgres(raw: RawPostgresConnection): NativeAddon { + class FakeBindingsHandle { + async postgres(): Promise { + return { connection: () => raw } + } + } + return { + BindingsHandle: FakeBindingsHandle as unknown as NativeAddon["BindingsHandle"], + version: () => "test", + } +} + function fakeAddon(): { addon: NativeAddon; constructions: unknown[] } { const constructions: unknown[] = [] @@ -53,6 +92,9 @@ function fakeAddon(): { addon: NativeAddon; constructions: unknown[] } { getInternalUrl: async () => "http://service.internal:8080", getPublicUrl: async () => null, } + const postgresHandle: RawPostgresHandle = { + connection: () => rawConnection("verify-full"), + } const bindings: RawBindingsHandle = { storage: async () => storageHandle, @@ -60,6 +102,7 @@ function fakeAddon(): { addon: NativeAddon; constructions: unknown[] } { queue: async () => queueHandle, vault: async () => vaultHandle, container: async () => containerHandle, + postgres: async () => postgresHandle, } class FakeBindingsHandle { @@ -71,6 +114,7 @@ function fakeAddon(): { addon: NativeAddon; constructions: unknown[] } { queue = bindings.queue vault = bindings.vault container = bindings.container + postgres = bindings.postgres } return { @@ -273,3 +317,103 @@ describe("createFactories method mapping", () => { await expect(container("database").getPublicUrl()).resolves.toBeNull() }) }) + +describe("createFactories postgres surface", () => { + it("passes every addon field through", async () => { + const { postgres } = createFactories(() => addonForPostgres(rawConnection("verify-full"))) + + const connection = await postgres("db").connection() + + expect(connection).toEqual({ + connectionString: "postgres://alien:pw@db.internal:5432/app?sslmode=verify-full", + ssl: { ca: [TEST_CA], rejectUnauthorized: true }, + host: "db.internal", + port: 5432, + database: "app", + username: "alien", + password: "pw", + sslmode: "verify-full", + }) + }) + + // `ssl` is what a node-postgres caller actually passes to the driver, so the mapping + // from the Rust SslMode has to be exact. + it.each([ + ["disable", false], + ["verify-full", { ca: [TEST_CA], rejectUnauthorized: true }], + ] as const)("derives ssl from sslmode %s", async (sslmode, expected) => { + const { postgres } = createFactories(() => addonForPostgres(rawConnection(sslmode))) + + const connection = await postgres("db").connection() + + expect(connection.sslmode).toBe(sslmode) + expect(connection.ssl).toEqual(expected) + }) + + it("uses system roots for verify-full when the addon supplies no CA", async () => { + const { postgres } = createFactories(() => addonForPostgres(rawConnection("verify-full", []))) + + await expect(postgres("db").connection()).resolves.toMatchObject({ + ssl: { rejectUnauthorized: true }, + sslmode: "verify-full", + }) + }) + + it("keeps CA verification but skips hostname matching for Cloud SQL PSC IPs", async () => { + const { postgres } = createFactories(() => addonForPostgres(rawConnection("verify-ca"))) + + const connection = await postgres("db").connection() + if (connection.sslmode !== "verify-ca") { + throw new Error(`expected verify-ca, received ${connection.sslmode}`) + } + + expect(connection.ssl.ca).toEqual([TEST_CA]) + expect(connection.ssl.rejectUnauthorized).toBe(true) + expect(connection.ssl.checkServerIdentity).toBeTypeOf("function") + expect(connection.ssl.checkServerIdentity()).toBeUndefined() + }) + + it("fails closed when a verified mode has no CA roots", async () => { + const { postgres } = createFactories(() => addonForPostgres(rawConnection("verify-ca", []))) + + const error = await postgres("db") + .connection() + .catch((e: unknown) => e) + + expect(error).toBeInstanceOf(AlienError) + expect((error as AlienError).code).toBe("INVALID_POSTGRES_TLS_CONFIG") + expect((error as AlienError).retryable).toBe(false) + }) + + it("reports a malformed CA element as a TLS contract error", async () => { + const { postgres } = createFactories(() => + addonForPostgres(rawConnection("verify-full", [TEST_CA, 42])), + ) + + const error = await postgres("db") + .connection() + .catch((e: unknown) => e) + + expect(error).toBeInstanceOf(AlienError) + expect((error as AlienError).code).toBe("INVALID_POSTGRES_TLS_CONFIG") + expect((error as AlienError).message).toContain("caCertificates[1] must be a string") + }) + + // An sslmode this wrapper doesn't know means wrapper/addon version skew. Silently + // defaulting would hand back a connection with the wrong TLS posture, so it must throw. + it("rejects an unknown sslmode from the addon rather than guessing a TLS posture", async () => { + const { postgres } = createFactories(() => addonForPostgres(rawConnection("prefer"))) + + const error = await postgres("db") + .connection() + .catch((e: unknown) => e) + + // The code is what a caller discriminates on: `guard` would otherwise flatten a + // bare `Error` into the generic BINDINGS_ERROR fallback. + expect(error).toBeInstanceOf(AlienError) + expect((error as AlienError).code).toBe("UNKNOWN_POSTGRES_SSLMODE") + expect((error as AlienError).retryable).toBe(false) + expect((error as AlienError).message).toContain("prefer") + expect((error as AlienError).message).toContain("disable, verify-ca, verify-full") + }) +}) diff --git a/packages/bindings/src/__tests__/loader.test.ts b/packages/bindings/src/__tests__/loader.test.ts index 7982b443f..96ed0b9b4 100644 --- a/packages/bindings/src/__tests__/loader.test.ts +++ b/packages/bindings/src/__tests__/loader.test.ts @@ -20,6 +20,9 @@ function addonReporting(version: string): NativeAddon { container(): never { throw new Error("not used by version validation") } + postgres(): never { + throw new Error("not used by version validation") + } }, version: () => version, } diff --git a/packages/bindings/src/errors.ts b/packages/bindings/src/errors.ts index 477f88c05..f491da88e 100644 --- a/packages/bindings/src/errors.ts +++ b/packages/bindings/src/errors.ts @@ -41,6 +41,45 @@ export const BindingNotConfiguredError = defineError({ httpStatusCode: 400, }) +/** + * Thrown when the native addon reports a Postgres `sslmode` this package does not + * recognize, which means the addon and this wrapper disagree about the Rust `SslMode` + * enum — a version skew between the two halves of the package. + * + * `sslmode` is the value the addon sent; `expected` is the set this wrapper accepts. + * Not retryable: a skew is fixed by aligning versions, not by trying again. + */ +export const UnknownPostgresSslModeError = defineError({ + code: "UNKNOWN_POSTGRES_SSLMODE", + context: z.object({ + sslmode: z.string(), + expected: z.array(z.string()), + }), + message: ({ sslmode, expected }) => + `@alienplatform/bindings received an unknown Postgres sslmode '${sslmode}' from the native addon; expected one of ${expected.join(", ")}.`, + retryable: false, + internal: false, +}) + +/** + * Thrown when the native addon reports a Postgres TLS policy that cannot be + * applied safely, such as a verified mode without any CA roots. + * + * This indicates wrapper/addon version skew or a malformed resolved binding. + * It is not retryable because retrying cannot repair the contract. + */ +export const InvalidPostgresTlsConfigError = defineError({ + code: "INVALID_POSTGRES_TLS_CONFIG", + context: z.object({ + sslmode: z.string(), + reason: z.string(), + }), + message: ({ sslmode, reason }) => + `@alienplatform/bindings received an invalid Postgres TLS configuration for sslmode '${sslmode}' from the native addon: ${reason}.`, + retryable: false, + internal: false, +}) + /** Fallback code for napi-internal errors whose message is not an envelope. */ const GENERIC_BINDINGS_CODE = "BINDINGS_ERROR" diff --git a/packages/bindings/src/factories.ts b/packages/bindings/src/factories.ts index 75c191d90..2d468e15a 100644 --- a/packages/bindings/src/factories.ts +++ b/packages/bindings/src/factories.ts @@ -13,12 +13,19 @@ * correctly even when destructured off the handle (`const { get } = storage(x)`). */ -import { unwrapNapiError } from "./errors.js" +import { + AlienError, + InvalidPostgresTlsConfigError, + UnknownPostgresSslModeError, + unwrapNapiError, +} from "./errors.js" import type { NativeAddon, RawBindingsHandle, RawContainerHandle, RawKvHandle, + RawPostgresConnection, + RawPostgresHandle, RawQueueHandle, RawStorageHandle, RawVaultHandle, @@ -28,6 +35,9 @@ import type { Kv, KvScanResult, KvSetOptions, + Postgres, + PostgresConnection, + PostgresSslMode, PresignedRequest, Queue, QueueMessage, @@ -168,6 +178,126 @@ function makeVault(handle: () => Promise): Vault { } } +const POSTGRES_SSL_MODES = { + disable: true, + "verify-ca": true, + "verify-full": true, +} satisfies Record + +function postgresSslModeLabel(sslmode: unknown): string { + return typeof sslmode === "string" ? sslmode : String(sslmode) +} + +function invalidPostgresTlsConfig(sslmode: unknown, reason: string): AlienError { + return new AlienError( + InvalidPostgresTlsConfigError.create({ + sslmode: postgresSslModeLabel(sslmode), + reason, + }).toOptions(), + ) +} + +function isPostgresSslMode(value: unknown): value is PostgresSslMode { + return typeof value === "string" && Object.hasOwn(POSTGRES_SSL_MODES, value) +} + +function hasAtLeastOne(values: T[]): values is [T, ...T[]] { + return values.length > 0 +} + +/** + * Decode the raw addon connection into the public discriminated union, deriving + * node-postgres TLS options from the Rust-resolved mode and provider roots. + * + * An unrecognized `sslmode` means the addon and this wrapper disagree about the + * `SslMode` enum — a version skew that must fail loudly rather than produce a + * connection with a silently wrong TLS posture. It throws + * {@link UnknownPostgresSslModeError} so a caller can discriminate it by `code`; + * `guard` passes an `AlienError` through untouched. + */ +function toPostgresConnection(raw: RawPostgresConnection): PostgresConnection { + if (!isPostgresSslMode(raw.sslmode)) { + throw new AlienError( + UnknownPostgresSslModeError.create({ + sslmode: postgresSslModeLabel(raw.sslmode), + expected: Object.keys(POSTGRES_SSL_MODES), + }).toOptions(), + ) + } + + if (!Array.isArray(raw.caCertificates)) { + throw invalidPostgresTlsConfig(raw.sslmode, "caCertificates must be an array") + } + + const caCertificates: string[] = [] + const rawCaCertificates: unknown[] = raw.caCertificates + for (const [index, certificate] of rawCaCertificates.entries()) { + if (typeof certificate !== "string") { + throw invalidPostgresTlsConfig(raw.sslmode, `caCertificates[${index}] must be a string`) + } + if (certificate.trim().length === 0) { + throw invalidPostgresTlsConfig(raw.sslmode, `caCertificates[${index}] cannot be empty`) + } + caCertificates.push(certificate) + } + + const fields = { + connectionString: raw.connectionString, + host: raw.host, + port: raw.port, + database: raw.database, + username: raw.username, + password: raw.password, + } + + switch (raw.sslmode) { + case "disable": + if (caCertificates.length > 0) { + throw invalidPostgresTlsConfig(raw.sslmode, "disable cannot carry CA certificates") + } + return { ...fields, sslmode: raw.sslmode, ssl: false } + case "verify-ca": + if (!hasAtLeastOne(caCertificates)) { + throw invalidPostgresTlsConfig( + raw.sslmode, + "verify-ca requires at least one CA certificate", + ) + } + return { + ...fields, + sslmode: raw.sslmode, + ssl: { + ca: caCertificates, + rejectUnauthorized: true, + // Node calls this only after the CA chain succeeds. Cloud SQL's + // per-instance CA authenticates the server even though its certificate + // cannot match the PSC consumer endpoint IP. + checkServerIdentity: (): undefined => undefined, + }, + } + case "verify-full": + return { + ...fields, + sslmode: raw.sslmode, + ssl: { + ...(caCertificates.length > 0 ? { ca: caCertificates } : {}), + rejectUnauthorized: true, + }, + } + default: { + const unhandledSslMode: never = raw.sslmode + return unhandledSslMode + } + } +} + +function makePostgres(handle: () => Promise): Postgres { + return { + connection: (): Promise => + guard(handle, async raw => toPostgresConnection(raw.connection())), + } +} + /** The public factory surface. */ export interface Factories { storage(name: string): Storage @@ -175,6 +305,7 @@ export interface Factories { queue(name: string): Queue vault(name: string): Vault container(name: string): Container + postgres(name: string): Postgres } /** Build the factories bound to a given addon provider. */ @@ -185,5 +316,6 @@ export function createFactories(getAddon: () => NativeAddon): Factories { queue: name => makeQueue(lazyHandle(getAddon, name, (b, n) => b.queue(n))), vault: name => makeVault(lazyHandle(getAddon, name, (b, n) => b.vault(n))), container: name => makeContainer(lazyHandle(getAddon, name, (b, n) => b.container(n))), + postgres: name => makePostgres(lazyHandle(getAddon, name, (b, n) => b.postgres(n))), } } diff --git a/packages/bindings/src/index.ts b/packages/bindings/src/index.ts index 4c18155d8..2644bd843 100644 --- a/packages/bindings/src/index.ts +++ b/packages/bindings/src/index.ts @@ -1,7 +1,7 @@ /** * `@alienplatform/bindings` — direct TypeScript bindings for Alien storage, kv, - * queue, vault, and linked containers, backed by an in-process napi-rs addon over the Rust - * `alien-bindings` crate. + * queue, vault, linked containers, and Postgres, backed by an in-process napi-rs addon + * over the Rust `alien-bindings` crate. * * Constructing a factory (`storage("x")`, `kv("y")`, …) does no I/O and needs no * addon; the native module loads on the first operation. The first operation @@ -24,8 +24,16 @@ export const queue = factories.queue export const vault = factories.vault /** Resolve the linked-container binding named `name`. */ export const container = factories.container +/** Resolve the Postgres binding named `name`. */ +export const postgres = factories.postgres -export { AlienError, BindingNotConfiguredError, defineError } from "./errors.js" +export { + AlienError, + BindingNotConfiguredError, + defineError, + InvalidPostgresTlsConfigError, + UnknownPostgresSslModeError, +} from "./errors.js" export type { Container, @@ -34,6 +42,10 @@ export type { KvScanResult, KvSetOptions, ObjectMeta, + Postgres, + PostgresConnection, + PostgresSslMode, + PostgresTlsOptions, PresignedRequest, Queue, QueueMessage, diff --git a/packages/bindings/src/loader.ts b/packages/bindings/src/loader.ts index 39d39827b..ef85825f9 100644 --- a/packages/bindings/src/loader.ts +++ b/packages/bindings/src/loader.ts @@ -111,6 +111,30 @@ export interface RawContainerHandle { getPublicUrl(): Promise } +/** + * Raw napi Postgres connection. `sslmode` is the exact query-parameter value the + * Rust `SslMode` emits. + */ +export interface RawPostgresConnection { + connectionString: string + host: string + port: number + database: string + username: string + password: string + sslmode: unknown + caCertificates: unknown +} + +/** + * Raw napi Postgres handle. `connection()` is synchronous: the Rust provider has + * already resolved everything (including reading a cloud backend's password from + * its secret store) by the time the handle exists. + */ +export interface RawPostgresHandle { + connection(): RawPostgresConnection +} + /** Raw napi vault handle. */ export interface RawVaultHandle { getSecret(name: string): Promise @@ -126,6 +150,7 @@ export interface RawBindingsHandle { queue(name: string): Promise vault(name: string): Promise container(name: string): Promise + postgres(name: string): Promise } /** The complete napi addon module surface consumed by the wrapper. */ diff --git a/packages/bindings/src/native.ts b/packages/bindings/src/native.ts index 4162d35de..17960edec 100644 --- a/packages/bindings/src/native.ts +++ b/packages/bindings/src/native.ts @@ -48,8 +48,16 @@ export const queue = factories.queue export const vault = factories.vault /** Resolve the linked-container binding named `name`. */ export const container = factories.container +/** Resolve the Postgres binding named `name`. */ +export const postgres = factories.postgres -export { AlienError, BindingNotConfiguredError, defineError } from "./errors.js" +export { + AlienError, + BindingNotConfiguredError, + defineError, + InvalidPostgresTlsConfigError, + UnknownPostgresSslModeError, +} from "./errors.js" export type { Container, @@ -58,6 +66,10 @@ export type { KvScanResult, KvSetOptions, ObjectMeta, + Postgres, + PostgresConnection, + PostgresSslMode, + PostgresTlsOptions, PresignedRequest, Queue, QueueMessage, diff --git a/packages/bindings/src/types.ts b/packages/bindings/src/types.ts index 97ea166d1..5427dccbd 100644 --- a/packages/bindings/src/types.ts +++ b/packages/bindings/src/types.ts @@ -152,6 +152,118 @@ export interface Container { getPublicUrl(): Promise } +/** + * TLS mode for a Postgres connection: `disable` for the local developer backend + * or an explicit BYO plaintext opt-out, `verify-full` for a BYO database with + * certificate and hostname verification, `verify-ca` for Cloud SQL over its + * Private Service Connect IP, and `verify-full` for Aurora and Flexible Server. + */ +export type PostgresSslMode = "disable" | "verify-ca" | "verify-full" + +/** TLS options for `verify-ca`, where the provider CA authenticates the server. */ +interface PostgresVerifyCaTlsOptions { + /** + * One or more PEM-encoded provider CA certificates. `verify-ca` cannot use the + * operating system trust store because the certificate is specific to the instance. + */ + ca: [string, ...string[]] + /** Always true: an untrusted certificate fails the connection. */ + rejectUnauthorized: true + /** + * Skip hostname matching after the CA chain succeeds because Cloud SQL's + * certificate does not contain the Private Service Connect IP being dialed. + */ + checkServerIdentity: () => undefined +} + +/** TLS options for full certificate and hostname verification. */ +interface PostgresVerifyFullTlsOptions { + /** + * PEM-encoded provider root CA certificates. Managed backends supply these; + * an external database can omit them to use the operating system trust store. + */ + ca?: string[] + /** Always true: an untrusted certificate fails the connection. */ + rejectUnauthorized: true + /** `verify-full` must never override Node's hostname verification. */ + checkServerIdentity?: never +} + +/** Verified TLS options ready to pass to node-postgres. */ +export type PostgresTlsOptions = PostgresVerifyCaTlsOptions | PostgresVerifyFullTlsOptions + +/** Connection fields shared by every Postgres TLS mode. */ +interface PostgresConnectionFields { + /** + * `postgres://user:password@host:port/database?sslmode=`. The username, + * password, and database are percent-encoded to the RFC 3986 unreserved set, so a + * generated password containing URL-special characters can never corrupt the URL. + */ + connectionString: string + /** Address to dial — the cluster writer endpoint for Aurora, the host elsewhere. */ + host: string + /** TCP port. */ + port: number + /** Database name. */ + database: string + /** Role to connect as. */ + username: string + /** + * Connection password. For the managed cloud backends this was read from the + * cloud secret store when the binding resolved; the binding itself only ever + * carries a locator for it. + */ + password: string +} + +interface PostgresDisableConnection extends PostgresConnectionFields { + sslmode: "disable" + ssl: false +} + +interface PostgresVerifyCaConnection extends PostgresConnectionFields { + sslmode: "verify-ca" + ssl: PostgresVerifyCaTlsOptions +} + +interface PostgresVerifyFullConnection extends PostgresConnectionFields { + sslmode: "verify-full" + ssl: PostgresVerifyFullTlsOptions +} + +/** + * Everything a Postgres driver needs to connect, discriminated by {@link sslmode}. + * + * For node-postgres, pass the individual fields with `ssl` rather than combining + * `connectionString` and `ssl`. node-postgres parses URL TLS parameters and can + * overwrite an explicit `ssl` object, including provider roots. + * + * External (BYO) bindings default to `verify-full`. A plaintext-only legacy server + * must be configured explicitly with `sslMode: "disable"` in its external binding. + */ +export type PostgresConnection = + | PostgresDisableConnection + | PostgresVerifyCaConnection + | PostgresVerifyFullConnection + +/** + * A resolved Postgres binding. + * + * Unlike the other kinds this exposes no operations: every Postgres backend speaks + * the same wire protocol, so the binding hands back connection details and the + * application connects with its own driver. + */ +export interface Postgres { + /** + * Resolve the connection details. + * + * For a managed cloud backend the first call reads the password from that cloud's + * secret store with the workload's own identity; the resolved value is then reused, + * so call the factory again to pick up a rotated password. + */ + connection(): Promise +} + /** A resolved vault (secrets) binding. */ export interface Vault { /** Get the secret named `name` as a string. */ diff --git a/packages/bindings/tests/helpers/local-binding-env.ts b/packages/bindings/tests/helpers/local-binding-env.ts index f1b08bc60..4380526a8 100644 --- a/packages/bindings/tests/helpers/local-binding-env.ts +++ b/packages/bindings/tests/helpers/local-binding-env.ts @@ -16,6 +16,11 @@ * → `{"service":"local-vault","vaultName":"","dataDir":""}` — * secrets live at `/secrets.json` * (see `crates/alien-bindings/src/providers/vault/local.rs`). + * - Postgres: `crates/alien-core/src/bindings/postgres.rs::LocalPostgresBinding` / + * `ExternalPostgresBinding` → + * `{"service":"local-postgres"|"external","host","port","database","username","password"}` + * — connection-only, so there is no directory or on-disk state + * (see `crates/alien-bindings/src/providers/postgres/local.rs`). * * Cross-checked against `crates/alien-local/src/local_bindings_provider.rs` * (which extracts exactly these fields from the parsed binding) and against @@ -174,6 +179,50 @@ export function localVaultBindingEnv( return fixture } +/** + * The connection fields every Postgres fixture uses. Shared by the `local-postgres` + * and `external` builders below. + * + * `password` deliberately contains every character the connection string must + * percent-encode: the RFC 3986 sub-delims `! * ' ( )` plus `@` and `/`, which would + * otherwise split the userinfo from the host. + * + * `scripts/run-bun-tests.mjs` seeds the same values into the child process env (Bun + * cannot mutate the C environment a native addon reads), so the two must stay in + * sync — `tests/postgres.test.ts` asserts the exact resolved URL and fails if they + * drift. + */ +export const POSTGRES_FIXTURE = { + host: "db.internal", + port: 5432, + database: "app", + username: "alien", + password: "a!b*c'd(e)f@/", +} as const + +/** Build the env for a `local-postgres` binding (resolves to `sslmode=disable`). */ +export function localPostgresBindingEnv(bindingName: string): void { + installBindingEnv({ + ...LOCAL_DEPLOYMENT_ENV, + [bindingEnvVarName(bindingName)]: JSON.stringify({ + service: "local-postgres", + ...POSTGRES_FIXTURE, + }), + }) +} + +/** Build the env for an `external` (BYO) Postgres binding using verified TLS. */ +export function externalPostgresBindingEnv(bindingName: string): void { + installBindingEnv({ + ...LOCAL_DEPLOYMENT_ENV, + [bindingEnvVarName(bindingName)]: JSON.stringify({ + service: "external", + ...POSTGRES_FIXTURE, + sslMode: "verify-full", + }), + }) +} + /** Build the env for a local linked-container binding. */ export function localContainerBindingEnv(bindingName: string): void { installBindingEnv({ diff --git a/packages/bindings/tests/postgres.test.ts b/packages/bindings/tests/postgres.test.ts new file mode 100644 index 000000000..5a01531c3 --- /dev/null +++ b/packages/bindings/tests/postgres.test.ts @@ -0,0 +1,85 @@ +/** + * Postgres behavioral tests through the REAL napi addon against the inline-password + * providers (`crates/alien-bindings/src/providers/postgres/local.rs`). No mocks: the + * binding JSON goes into the environment and the Rust resolver produces the connection. + * + * The three cloud backends (Aurora / Cloud SQL / Flexible Server) resolve their password + * from a cloud secret store, which needs real cloud credentials — they are covered by + * unit tests with a faked secret client in each provider module, and end to end only + * against a deployed stack (see the PR notes). + */ + +import { afterAll, describe, expect, it } from "vitest" +import { AlienError, postgres } from "../src/index.js" +import { + POSTGRES_FIXTURE, + cleanupTempDirs, + externalPostgresBindingEnv, + localPostgresBindingEnv, +} from "./helpers/local-binding-env.js" + +const isBun = process.env.BUN_EXPECTED === "1" + +function localName(): string { + const name = isBun ? "bun-postgres-local" : "postgres-local" + if (!isBun) localPostgresBindingEnv(name) + return name +} + +function externalName(): string { + const name = isBun ? "bun-postgres-external" : "postgres-external" + if (!isBun) externalPostgresBindingEnv(name) + return name +} + +describe("postgres (inline-password providers)", () => { + afterAll(() => { + cleanupTempDirs() + }) + + // The exact URL is the contract: the password's RFC 3986 sub-delims (! * ' ( )) and + // its `@` / `/` must be percent-encoded, or the URL would be unparseable or point at + // the wrong host. Pinned as a literal so any change to the encoding or to the shared + // fixture fails here rather than silently producing a different connection. + it("resolves a local binding to a sslmode=disable connection with an encoded password", async () => { + const connection = await postgres(localName()).connection() + + expect(connection.connectionString).toBe( + "postgres://alien:a%21b%2Ac%27d%28e%29f%40%2F@db.internal:5432/app?sslmode=disable", + ) + expect(connection.sslmode).toBe("disable") + expect(connection.ssl).toBe(false) + }) + + it("resolves an external binding to verified TLS", async () => { + const connection = await postgres(externalName()).connection() + + expect(connection.connectionString).toBe( + "postgres://alien:a%21b%2Ac%27d%28e%29f%40%2F@db.internal:5432/app?sslmode=verify-full", + ) + expect(connection.sslmode).toBe("verify-full") + expect(connection.ssl).toEqual({ rejectUnauthorized: true }) + }) + + // The individual fields are what a driver taking separate arguments gets, so they must + // carry the *un-encoded* values the connection string encodes. + it("returns the un-encoded connection fields alongside the URL", async () => { + const connection = await postgres(localName()).connection() + + expect(connection.host).toBe(POSTGRES_FIXTURE.host) + expect(connection.port).toBe(POSTGRES_FIXTURE.port) + expect(connection.database).toBe(POSTGRES_FIXTURE.database) + expect(connection.username).toBe(POSTGRES_FIXTURE.username) + expect(connection.password).toBe(POSTGRES_FIXTURE.password) + }) + + it("reports an unconfigured binding rather than resolving an empty connection", async () => { + const error = await postgres("postgres-not-configured") + .connection() + .catch((e: unknown) => e) + + expect(error).toBeInstanceOf(AlienError) + expect((error as AlienError).code).toBe("BINDING_NOT_CONFIGURED") + expect((error as AlienError).message).toContain("ALIEN_POSTGRES_NOT_CONFIGURED_BINDING") + }) +}) diff --git a/packages/package-layout/fixture/src/imports.ts b/packages/package-layout/fixture/src/imports.ts index be5375688..f2ebcda44 100644 --- a/packages/package-layout/fixture/src/imports.ts +++ b/packages/package-layout/fixture/src/imports.ts @@ -48,6 +48,8 @@ const SDK_FACADE_EXPORTS = [ "kv", "queue", "vault", + "container", + "postgres", ] as const // The sdk contract's "error re-exports" row: BindingNotConfiguredError (from @@ -142,7 +144,11 @@ const BINDINGS_EXPORTS = [ "kv", "queue", "vault", + "container", + "postgres", "BindingNotConfiguredError", + "InvalidPostgresTlsConfigError", + "UnknownPostgresSslModeError", "AlienError", "defineError", ] as const @@ -183,7 +189,10 @@ async function checkBindings(): Promise { package: "bindings", status: missing.length === 0 ? "pass" : "fail", reason: missing.length === 0 ? "ok" : "missing pinned exports", - evidence: missing.length === 0 ? "resolved storage/kv/queue/vault + error" : missing.join(", "), + evidence: + missing.length === 0 + ? "resolved storage/kv/queue/vault/container/postgres + error" + : missing.join(", "), }) // The first operation against an unconfigured binding must throw diff --git a/packages/sdk/PACKAGE_LAYOUT.md b/packages/sdk/PACKAGE_LAYOUT.md index ea1016a04..535facfed 100644 --- a/packages/sdk/PACKAGE_LAYOUT.md +++ b/packages/sdk/PACKAGE_LAYOUT.md @@ -34,8 +34,9 @@ in [`@alienplatform/bindings`](../bindings/PACKAGE_LAYOUT.md). | `onQueueMessage` | function | Worker queue-message handler registrar | Signature pinned by `src/worker-runtime`. | | `waitUntil` | function | `waitUntil(promise): void` | Extend Worker task lifetime past the response. | | handler types | type | `StorageEvent`, `StorageEventType`, `CronEvent`, `QueueMessage`, `QueueMessageEvent`, `ScheduledEvent` | The event/handler types for the APIs above. | -| `storage`, `kv`, `queue`, `vault`, `container` | function | re-export from `@alienplatform/bindings` | Facade re-export of the binding factories. | -| `Storage`, `Kv`, `Queue`, `Vault` | type | re-export from `@alienplatform/bindings` | Facade re-export of the instance types. | +| `storage`, `kv`, `queue`, `vault`, `container`, `postgres` | function | re-export from `@alienplatform/bindings` | Facade re-export of the binding factories. | +| `Storage`, `Kv`, `Queue`, `Vault`, `Container`, `Postgres` | type | re-export from `@alienplatform/bindings` | Facade re-export of the instance types. | +| `PostgresConnection`, `PostgresSslMode` | type | re-export from `@alienplatform/bindings` | Facade re-export of the Postgres connection shape. | | error re-exports | error | from `@alienplatform/bindings` and `@alienplatform/core` | Includes `BindingNotConfiguredError`; `AlienError`. | ### Removed from the root surface @@ -50,8 +51,10 @@ These names must remain absent from `"."`: - gRPC-era binding errors that the direct bindings package replaces: `GrpcConnectionError`, `GrpcCallError`, `BindingNotFoundError` (superseded by `BINDING_NOT_CONFIGURED` in `@alienplatform/bindings`). -- `getPostgresConnection` / `PostgresConnection`; applications resolve their own - Postgres connections. +- `getPostgresConnection` — the SDK-local Postgres resolver. Postgres connections + come from the `postgres()` binding factory re-exported above, which resolves every + backend (including reading a cloud backend's password from its secret store) in the + Rust provider. ## Subpaths diff --git a/packages/sdk/src/index.ts b/packages/sdk/src/index.ts index 1937311b2..fc03dabd8 100644 --- a/packages/sdk/src/index.ts +++ b/packages/sdk/src/index.ts @@ -3,7 +3,7 @@ * * It provides the Worker handler APIs (`command`, `onStorageEvent`, * `onCronEvent`, `onQueueMessage`, `waitUntil`) and re-exports the app-facing - * binding factories (`storage`, `kv`, `queue`, `vault`) from + * binding factories (`storage`, `kv`, `queue`, `vault`, `container`, `postgres`) from * `@alienplatform/bindings`, so a Worker author installs one package. * * Worker protocol dependencies (nice-grpc, generated Worker protocol clients) @@ -55,8 +55,17 @@ export type { // Binding factories — re-exported from @alienplatform/bindings // ============================================================================ -export { storage, kv, queue, vault, container } from "@alienplatform/bindings" -export type { Storage, Kv, Queue, Vault, Container } from "@alienplatform/bindings" +export { storage, kv, queue, vault, container, postgres } from "@alienplatform/bindings" +export type { + Storage, + Kv, + Queue, + Vault, + Container, + Postgres, + PostgresConnection, + PostgresSslMode, +} from "@alienplatform/bindings" // ============================================================================ // Errors — re-exported from @alienplatform/bindings and @alienplatform/core diff --git a/tests/e2e/test-apps/comprehensive-typescript/src/handlers/postgres.ts b/tests/e2e/test-apps/comprehensive-typescript/src/handlers/postgres.ts index 231ff67e0..a029ae4af 100644 --- a/tests/e2e/test-apps/comprehensive-typescript/src/handlers/postgres.ts +++ b/tests/e2e/test-apps/comprehensive-typescript/src/handlers/postgres.ts @@ -1,4 +1,4 @@ -import { AlienError } from "@alienplatform/core" +import { postgres } from "@alienplatform/sdk" import { Hono } from "hono" import { toExternalOperationError } from "../helpers.js" @@ -12,91 +12,20 @@ type BunSqlClient = { } declare const Bun: { SQL: new (connectionString: string) => BunSqlClient } -// Postgres connection resolution, inlined here from the (now removed) -// `@alienplatform/sdk` `getPostgresConnection`. The SDK facade is Worker -// handler APIs + binding factories only; connection-only Postgres resolution -// lives with its sole consumer. This e2e app only deploys local Postgres -// (plaintext, `sslmode=disable`), so only the non-cloud variants are inlined; -// cloud secret resolution (Aurora/Cloud SQL/Flexible Server) is not exercised -// here and is intentionally rejected rather than pulling in cloud secret SDKs. - -/** Matches the Rust `binding_env_var_name`: `ALIEN__BINDING`, hyphens to underscores. */ -function bindingEnvVarName(bindingName: string): string { - return `ALIEN_${bindingName.replace(/-/g, "_").toUpperCase()}_BINDING` -} - -/** - * Percent-encode a userinfo/path component to the RFC 3986 unreserved set, - * matching the Rust resolver. - */ -function encodeUserinfo(value: string): string { - return encodeURIComponent(value).replace( - /[!'()*]/g, - character => `%${character.charCodeAt(0).toString(16).toUpperCase().padStart(2, "0")}`, - ) -} - -function connectionString( - host: string, - params: { port: number; database: string; username: string }, - password: string, - sslmode: "disable" | "prefer", -): string { - const user = encodeUserinfo(params.username) - const pass = encodeUserinfo(password) - return `postgres://${user}:${pass}@${host}:${params.port}/${encodeUserinfo(params.database)}?sslmode=${sslmode}` -} - -/** Resolve the connection string for a linked local/external Postgres database. */ -function resolveConnectionString(bindingName: string): string { - const raw = process.env[bindingEnvVarName(bindingName)] - if (!raw) { - throw new AlienError({ - code: "POSTGRES_BINDING_NOT_FOUND", - message: `Postgres binding '${bindingName}' is not configured`, - retryable: false, - internal: false, - httpStatusCode: 404, - context: { bindingName }, - }) - } - const binding = JSON.parse(raw) as { - service: string - host: string - port: number - database: string - username: string - password?: string - } - const params = { port: binding.port, database: binding.database, username: binding.username } - switch (binding.service) { - case "local-postgres": - return connectionString(binding.host, params, binding.password ?? "", "disable") - case "external": - return connectionString(binding.host, params, binding.password ?? "", "prefer") - default: - throw new AlienError({ - code: "POSTGRES_BINDING_UNSUPPORTED", - message: `Postgres backend '${binding.service}' is not exercised by the e2e test app`, - retryable: false, - internal: false, - httpStatusCode: 400, - context: { service: binding.service }, - }) - } -} - const app = new Hono() -// Exercise a Postgres binding end to end: resolve the connection, open a real driver connection, and -// run a query. The binding is connection-only by design (no gRPC surface), so proving the resource -// works means actually speaking the wire protocol against it. +// Exercise a Postgres binding end to end: resolve the connection through the real binding, open a +// driver connection, and run a query. The binding is connection-only by design (no gRPC surface), +// so proving the resource works means actually speaking the wire protocol against it. app.post("/postgres-test/:bindingName", async c => { const bindingName = c.req.param("bindingName") try { - const url = resolveConnectionString(bindingName) - // Local Postgres is plaintext (sslmode=disable); the connection string carries the credentials. - const sql = new Bun.SQL(url) + // The binding resolves every backend, including reading a cloud backend's password from its + // secret store. This app only deploys local Postgres (plaintext, `sslmode=disable`), so the + // connection string alone is enough here; a cloud backend would additionally need the + // connection's `ssl` value passed to the driver as individual fields. + const { connectionString } = await postgres(bindingName).connection() + const sql = new Bun.SQL(connectionString) try { const rows = await sql`SELECT 1 AS one` const one = rows[0]?.one