itsi-scheduler 0.1.5 → 0.1.11
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of itsi-scheduler might be problematic. Click here for more details.
- checksums.yaml +4 -4
- data/Cargo.lock +12 -12
- data/ext/itsi_error/src/from.rs +26 -29
- data/ext/itsi_error/src/lib.rs +1 -1
- data/ext/itsi_rb_helpers/src/lib.rs +27 -4
- data/ext/itsi_server/Cargo.lock +2956 -0
- data/ext/itsi_server/Cargo.toml +6 -2
- data/ext/itsi_server/src/env.rs +43 -0
- data/ext/itsi_server/src/lib.rs +75 -1
- data/ext/itsi_server/src/request/itsi_request.rs +39 -18
- data/ext/itsi_server/src/response/itsi_response.rs +14 -4
- data/ext/itsi_server/src/server/bind.rs +20 -15
- data/ext/itsi_server/src/server/itsi_server.rs +147 -103
- data/ext/itsi_server/src/server/listener.rs +99 -108
- data/ext/itsi_server/src/server/process_worker.rs +10 -3
- data/ext/itsi_server/src/server/serve_strategy/cluster_mode.rs +15 -9
- data/ext/itsi_server/src/server/serve_strategy/single_mode.rs +144 -115
- data/ext/itsi_server/src/server/signal.rs +4 -0
- data/ext/itsi_server/src/server/thread_worker.rs +55 -24
- data/ext/itsi_server/src/server/tls/locked_dir_cache.rs +55 -17
- data/ext/itsi_server/src/server/tls.rs +104 -28
- data/ext/itsi_tracing/src/lib.rs +18 -1
- data/lib/itsi/scheduler/version.rb +1 -1
- metadata +4 -4
- data/ext/itsi_server/src/server/itsi_ca/itsi_ca.crt +0 -13
- data/ext/itsi_server/src/server/itsi_ca/itsi_ca.key +0 -5
@@ -1,34 +1,68 @@
|
|
1
1
|
use async_trait::async_trait;
|
2
|
-
use fs2::FileExt;
|
3
|
-
use
|
2
|
+
use fs2::FileExt;
|
3
|
+
use parking_lot::Mutex;
|
4
|
+
use std::fs::{self, OpenOptions};
|
4
5
|
use std::io::Error as IoError;
|
5
6
|
use std::path::{Path, PathBuf};
|
6
7
|
use tokio_rustls_acme::caches::DirCache;
|
7
8
|
use tokio_rustls_acme::{AccountCache, CertCache};
|
8
9
|
|
10
|
+
use crate::env::ITSI_ACME_LOCK_FILE_NAME;
|
11
|
+
|
9
12
|
/// A wrapper around DirCache that locks a file before writing cert/account data.
|
10
13
|
pub struct LockedDirCache<P: AsRef<Path> + Send + Sync> {
|
11
14
|
inner: DirCache<P>,
|
12
15
|
lock_path: PathBuf,
|
16
|
+
current_lock: Mutex<Option<std::fs::File>>,
|
13
17
|
}
|
14
18
|
|
15
19
|
impl<P: AsRef<Path> + Send + Sync> LockedDirCache<P> {
|
16
20
|
pub fn new(dir: P) -> Self {
|
17
21
|
let dir_path = dir.as_ref().to_path_buf();
|
18
|
-
|
22
|
+
std::fs::create_dir_all(&dir_path).unwrap();
|
23
|
+
let lock_path = dir_path.join(&*ITSI_ACME_LOCK_FILE_NAME);
|
24
|
+
Self::touch_file(&lock_path).expect("Failed to create lock file");
|
25
|
+
|
19
26
|
Self {
|
20
27
|
inner: DirCache::new(dir),
|
21
28
|
lock_path,
|
29
|
+
current_lock: Mutex::new(None),
|
30
|
+
}
|
31
|
+
}
|
32
|
+
|
33
|
+
fn touch_file(path: &PathBuf) -> std::io::Result<()> {
|
34
|
+
if let Some(parent) = path.parent() {
|
35
|
+
fs::create_dir_all(parent)?;
|
22
36
|
}
|
37
|
+
fs::OpenOptions::new()
|
38
|
+
.create(true)
|
39
|
+
.write(true)
|
40
|
+
.truncate(true)
|
41
|
+
.open(path)?;
|
42
|
+
Ok(())
|
23
43
|
}
|
24
44
|
|
25
|
-
fn lock_exclusive(&self) -> Result<
|
45
|
+
fn lock_exclusive(&self) -> Result<(), IoError> {
|
46
|
+
if self.current_lock.lock().is_some() {
|
47
|
+
return Ok(());
|
48
|
+
}
|
49
|
+
|
50
|
+
if let Some(parent) = self.lock_path.parent() {
|
51
|
+
std::fs::create_dir_all(parent)?;
|
52
|
+
}
|
26
53
|
let lockfile = OpenOptions::new()
|
54
|
+
.create(true)
|
27
55
|
.write(true)
|
28
56
|
.truncate(true)
|
29
57
|
.open(&self.lock_path)?;
|
30
58
|
lockfile.lock_exclusive()?;
|
31
|
-
|
59
|
+
*self.current_lock.lock() = Some(lockfile);
|
60
|
+
Ok(())
|
61
|
+
}
|
62
|
+
|
63
|
+
fn unlock(&self) -> Result<(), IoError> {
|
64
|
+
self.current_lock.lock().take();
|
65
|
+
Ok(())
|
32
66
|
}
|
33
67
|
}
|
34
68
|
|
@@ -41,8 +75,14 @@ impl<P: AsRef<Path> + Send + Sync> CertCache for LockedDirCache<P> {
|
|
41
75
|
domains: &[String],
|
42
76
|
directory_url: &str,
|
43
77
|
) -> Result<Option<Vec<u8>>, Self::EC> {
|
44
|
-
|
45
|
-
self.inner.load_cert(domains, directory_url).await
|
78
|
+
self.lock_exclusive()?;
|
79
|
+
let result = self.inner.load_cert(domains, directory_url).await;
|
80
|
+
|
81
|
+
if let Ok(Some(_)) = result {
|
82
|
+
self.unlock()?;
|
83
|
+
}
|
84
|
+
|
85
|
+
result
|
46
86
|
}
|
47
87
|
|
48
88
|
async fn store_cert(
|
@@ -52,13 +92,14 @@ impl<P: AsRef<Path> + Send + Sync> CertCache for LockedDirCache<P> {
|
|
52
92
|
cert: &[u8],
|
53
93
|
) -> Result<(), Self::EC> {
|
54
94
|
// Acquire the lock before storing
|
55
|
-
|
95
|
+
self.lock_exclusive()?;
|
56
96
|
|
57
97
|
// Perform the store operation
|
58
98
|
let result = self.inner.store_cert(domains, directory_url, cert).await;
|
59
99
|
|
60
|
-
|
61
|
-
|
100
|
+
if let Ok(()) = result {
|
101
|
+
self.unlock()?;
|
102
|
+
}
|
62
103
|
result
|
63
104
|
}
|
64
105
|
}
|
@@ -72,6 +113,7 @@ impl<P: AsRef<Path> + Send + Sync> AccountCache for LockedDirCache<P> {
|
|
72
113
|
contact: &[String],
|
73
114
|
directory_url: &str,
|
74
115
|
) -> Result<Option<Vec<u8>>, Self::EA> {
|
116
|
+
self.lock_exclusive()?;
|
75
117
|
self.inner.load_account(contact, directory_url).await
|
76
118
|
}
|
77
119
|
|
@@ -81,14 +123,10 @@ impl<P: AsRef<Path> + Send + Sync> AccountCache for LockedDirCache<P> {
|
|
81
123
|
directory_url: &str,
|
82
124
|
account: &[u8],
|
83
125
|
) -> Result<(), Self::EA> {
|
84
|
-
|
126
|
+
self.lock_exclusive()?;
|
85
127
|
|
86
|
-
|
87
|
-
.inner
|
128
|
+
self.inner
|
88
129
|
.store_account(contact, directory_url, account)
|
89
|
-
.await
|
90
|
-
|
91
|
-
let _ = fs2::FileExt::unlock(&lockfile);
|
92
|
-
result
|
130
|
+
.await
|
93
131
|
}
|
94
132
|
}
|
@@ -2,21 +2,29 @@ use base64::{engine::general_purpose, Engine as _};
|
|
2
2
|
use itsi_error::Result;
|
3
3
|
use itsi_tracing::info;
|
4
4
|
use locked_dir_cache::LockedDirCache;
|
5
|
-
use rcgen::{
|
6
|
-
|
5
|
+
use rcgen::{
|
6
|
+
generate_simple_self_signed, CertificateParams, CertifiedKey, DnType, KeyPair, SanType,
|
7
|
+
};
|
8
|
+
use rustls::{
|
9
|
+
pki_types::{CertificateDer, PrivateKeyDer},
|
10
|
+
ClientConfig, RootCertStore,
|
11
|
+
};
|
7
12
|
use rustls_pemfile::{certs, pkcs8_private_keys};
|
8
13
|
use std::{
|
9
14
|
collections::HashMap,
|
10
|
-
|
15
|
+
fs,
|
11
16
|
io::{BufReader, Error},
|
12
17
|
sync::Arc,
|
13
18
|
};
|
14
19
|
use tokio::sync::Mutex;
|
15
20
|
use tokio_rustls::{rustls::ServerConfig, TlsAcceptor};
|
16
21
|
use tokio_rustls_acme::{AcmeAcceptor, AcmeConfig, AcmeState};
|
22
|
+
|
23
|
+
use crate::env::{
|
24
|
+
ITSI_ACME_CACHE_DIR, ITSI_ACME_CA_PEM_PATH, ITSI_ACME_CONTACT_EMAIL, ITSI_ACME_DIRECTORY_URL,
|
25
|
+
ITSI_LOCAL_CA_DIR,
|
26
|
+
};
|
17
27
|
mod locked_dir_cache;
|
18
|
-
const ITS_CA_CERT: &str = include_str!("./itsi_ca/itsi_ca.crt");
|
19
|
-
const ITS_CA_KEY: &str = include_str!("./itsi_ca/itsi_ca.key");
|
20
28
|
|
21
29
|
#[derive(Clone)]
|
22
30
|
pub enum ItsiTlsAcceptor {
|
@@ -28,35 +36,72 @@ pub enum ItsiTlsAcceptor {
|
|
28
36
|
),
|
29
37
|
}
|
30
38
|
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
39
|
+
/// Generates a TLS configuration based on either :
|
40
|
+
/// * Input "cert" and "key" options (either paths or Base64-encoded strings) or
|
41
|
+
/// * Performs automatic certificate generation/retrieval. Generated certs use an internal self-signed Isti CA.
|
42
|
+
///
|
43
|
+
/// If a non-local host or optional domain parameter is provided,
|
44
|
+
/// an automated certificate will attempt to be fetched using let's encrypt.
|
36
45
|
pub fn configure_tls(
|
37
46
|
host: &str,
|
38
47
|
query_params: &HashMap<String, String>,
|
39
48
|
) -> Result<ItsiTlsAcceptor> {
|
40
49
|
let domains = query_params
|
41
50
|
.get("domains")
|
42
|
-
.map(|v| v.split(',').map(String::from).collect::<Vec<_>>())
|
51
|
+
.map(|v| v.split(',').map(String::from).collect::<Vec<_>>())
|
52
|
+
.or_else(|| query_params.get("domain").map(|v| vec![v.to_string()]));
|
43
53
|
|
44
|
-
if query_params.get("cert").
|
54
|
+
if query_params.get("cert").is_some_and(|c| c == "acme") {
|
45
55
|
if let Some(domains) = domains {
|
46
|
-
let directory_url =
|
47
|
-
.unwrap_or_else(|_| "https://acme-v02.api.letsencrypt.org/directory".to_string());
|
56
|
+
let directory_url = &*ITSI_ACME_DIRECTORY_URL;
|
48
57
|
info!(
|
49
58
|
domains = format!("{:?}", domains),
|
50
59
|
directory_url, "Requesting acme cert"
|
51
60
|
);
|
52
|
-
let
|
53
|
-
.
|
54
|
-
.
|
55
|
-
.
|
56
|
-
.
|
57
|
-
|
61
|
+
let acme_contact_email = query_params
|
62
|
+
.get("acme_email")
|
63
|
+
.map(|s| s.to_string())
|
64
|
+
.or_else(|| (*ITSI_ACME_CONTACT_EMAIL).as_ref().ok().map(|s| s.to_string()))
|
65
|
+
.ok_or_else(|| itsi_error::ItsiError::ArgumentError(
|
66
|
+
"acme_email query param or ITSI_ACME_CONTACT_EMAIL must be set before you can auto-generate let's encrypt certificates".to_string(),
|
67
|
+
))?;
|
68
|
+
|
69
|
+
let acme_config = AcmeConfig::new(domains)
|
70
|
+
.contact([format!("mailto:{}", acme_contact_email)])
|
71
|
+
.cache(LockedDirCache::new(&*ITSI_ACME_CACHE_DIR))
|
72
|
+
.directory(directory_url);
|
73
|
+
|
74
|
+
let acme_state = if let Ok(ca_pem_path) = &*ITSI_ACME_CA_PEM_PATH {
|
75
|
+
let mut root_cert_store = RootCertStore::empty();
|
76
|
+
|
77
|
+
let ca_pem = fs::read(ca_pem_path).expect("failed to read CA pem file");
|
78
|
+
let mut ca_reader = BufReader::new(&ca_pem[..]);
|
79
|
+
let der_certs: Vec<CertificateDer> = certs(&mut ca_reader)
|
80
|
+
.collect::<std::result::Result<Vec<CertificateDer>, _>>()
|
81
|
+
.map_err(|e| {
|
82
|
+
itsi_error::ItsiError::ArgumentError(format!(
|
83
|
+
"Invalid ACME CA Pem path {:?}",
|
84
|
+
e
|
85
|
+
))
|
86
|
+
})?;
|
87
|
+
root_cert_store.add_parsable_certificates(der_certs);
|
88
|
+
|
89
|
+
let client_config = ClientConfig::builder()
|
90
|
+
.with_root_certificates(root_cert_store)
|
91
|
+
.with_no_client_auth();
|
92
|
+
acme_config
|
93
|
+
.client_tls_config(Arc::new(client_config))
|
94
|
+
.state()
|
95
|
+
} else {
|
96
|
+
acme_config.state()
|
97
|
+
};
|
98
|
+
|
99
|
+
let mut rustls_config = ServerConfig::builder()
|
58
100
|
.with_no_client_auth()
|
59
101
|
.with_cert_resolver(acme_state.resolver());
|
102
|
+
|
103
|
+
rustls_config.alpn_protocols = vec![b"h2".to_vec(), b"http/1.1".to_vec()];
|
104
|
+
|
60
105
|
let acceptor = acme_state.acceptor();
|
61
106
|
return Ok(ItsiTlsAcceptor::Automatic(
|
62
107
|
acceptor,
|
@@ -73,7 +118,7 @@ pub fn configure_tls(
|
|
73
118
|
let key = load_private_key(key_path);
|
74
119
|
(certs, key)
|
75
120
|
} else {
|
76
|
-
generate_ca_signed_cert(vec![host.to_owned()])?
|
121
|
+
generate_ca_signed_cert(domains.unwrap_or(vec![host.to_owned()]))?
|
77
122
|
};
|
78
123
|
|
79
124
|
let mut config = ServerConfig::builder()
|
@@ -144,10 +189,19 @@ pub fn load_private_key(path: &str) -> PrivateKeyDer<'static> {
|
|
144
189
|
pub fn generate_ca_signed_cert(
|
145
190
|
domains: Vec<String>,
|
146
191
|
) -> Result<(Vec<CertificateDer<'static>>, PrivateKeyDer<'static>)> {
|
147
|
-
info!(
|
192
|
+
info!(
|
193
|
+
domains = format!("{}", domains.join(", ")),
|
194
|
+
"Self signed cert",
|
195
|
+
);
|
196
|
+
info!(
|
197
|
+
"Add {} to your system's trusted cert store to resolve certificate errors.",
|
198
|
+
format!("{}/itsi_dev_ca.crt", ITSI_LOCAL_CA_DIR.to_str().unwrap())
|
199
|
+
);
|
200
|
+
info!("Dev CA path can be overridden by setting env var: `ITSI_LOCAL_CA_DIR`.");
|
201
|
+
let (ca_key_pem, ca_cert_pem) = get_or_create_local_dev_ca()?;
|
148
202
|
|
149
|
-
let ca_kp = KeyPair::from_pem(
|
150
|
-
let ca_cert = CertificateParams::from_ca_cert_pem(
|
203
|
+
let ca_kp = KeyPair::from_pem(&ca_key_pem).expect("Failed to load CA key");
|
204
|
+
let ca_cert = CertificateParams::from_ca_cert_pem(&ca_cert_pem)
|
151
205
|
.expect("Failed to parse embedded CA certificate")
|
152
206
|
.self_signed(&ca_kp)
|
153
207
|
.expect("Failed to self-sign embedded CA cert");
|
@@ -155,10 +209,6 @@ pub fn generate_ca_signed_cert(
|
|
155
209
|
let ee_key = KeyPair::generate_for(&rcgen::PKCS_ECDSA_P256_SHA256).unwrap();
|
156
210
|
let mut ee_params = CertificateParams::default();
|
157
211
|
|
158
|
-
info!(
|
159
|
-
"Generated certificate will be valid for domains {:?}",
|
160
|
-
domains
|
161
|
-
);
|
162
212
|
use std::net::IpAddr;
|
163
213
|
|
164
214
|
ee_params.subject_alt_names = domains
|
@@ -187,3 +237,29 @@ pub fn generate_ca_signed_cert(
|
|
187
237
|
PrivateKeyDer::try_from(ee_key.serialize_der()).unwrap(),
|
188
238
|
))
|
189
239
|
}
|
240
|
+
|
241
|
+
fn get_or_create_local_dev_ca() -> Result<(String, String)> {
|
242
|
+
let ca_dir = &*ITSI_LOCAL_CA_DIR;
|
243
|
+
fs::create_dir_all(ca_dir)?;
|
244
|
+
|
245
|
+
let key_path = ca_dir.join("itsi_dev_ca.key");
|
246
|
+
let cert_path = ca_dir.join("itsi_dev_ca.crt");
|
247
|
+
|
248
|
+
if key_path.exists() && cert_path.exists() {
|
249
|
+
// Already have a local CA
|
250
|
+
let key_pem = fs::read_to_string(&key_path)?;
|
251
|
+
let cert_pem = fs::read_to_string(&cert_path)?;
|
252
|
+
|
253
|
+
Ok((key_pem, cert_pem))
|
254
|
+
} else {
|
255
|
+
let subject_alt_names = vec!["dev.itsi.fyi".to_string(), "localhost".to_string()];
|
256
|
+
|
257
|
+
let CertifiedKey { cert, key_pair } =
|
258
|
+
generate_simple_self_signed(subject_alt_names).unwrap();
|
259
|
+
|
260
|
+
fs::write(&key_path, key_pair.serialize_pem())?;
|
261
|
+
fs::write(&cert_path, cert.pem())?;
|
262
|
+
|
263
|
+
Ok((key_pair.serialize_pem(), cert.pem()))
|
264
|
+
}
|
265
|
+
}
|
data/ext/itsi_tracing/src/lib.rs
CHANGED
@@ -1,11 +1,13 @@
|
|
1
1
|
use std::env;
|
2
2
|
|
3
3
|
use atty::{Stream, is};
|
4
|
+
use tracing::level_filters::LevelFilter;
|
4
5
|
pub use tracing::{debug, error, info, trace, warn};
|
5
6
|
pub use tracing_attributes::instrument; // Explicitly export from tracing-attributes
|
6
7
|
use tracing_subscriber::{
|
7
|
-
EnvFilter,
|
8
|
+
EnvFilter, Layer,
|
8
9
|
fmt::{self, format},
|
10
|
+
layer::SubscriberExt,
|
9
11
|
};
|
10
12
|
|
11
13
|
#[instrument]
|
@@ -39,3 +41,18 @@ pub fn init() {
|
|
39
41
|
.init();
|
40
42
|
}
|
41
43
|
}
|
44
|
+
|
45
|
+
pub fn run_silently<F, R>(f: F) -> R
|
46
|
+
where
|
47
|
+
F: FnOnce() -> R,
|
48
|
+
{
|
49
|
+
// Build a minimal subscriber that filters *everything* out
|
50
|
+
let no_op_subscriber =
|
51
|
+
tracing_subscriber::registry().with(fmt::layer().with_filter(LevelFilter::OFF));
|
52
|
+
|
53
|
+
// Turn that subscriber into a `Dispatch`
|
54
|
+
let no_op_dispatch = tracing::dispatcher::Dispatch::new(no_op_subscriber);
|
55
|
+
|
56
|
+
// Temporarily set `no_op_dispatch` as the *default* within this closure
|
57
|
+
tracing::dispatcher::with_default(&no_op_dispatch, f)
|
58
|
+
}
|
metadata
CHANGED
@@ -1,13 +1,13 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: itsi-scheduler
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.1.
|
4
|
+
version: 0.1.11
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Wouter Coppieters
|
8
8
|
bindir: exe
|
9
9
|
cert_chain: []
|
10
|
-
date: 2025-03-
|
10
|
+
date: 2025-03-17 00:00:00.000000000 Z
|
11
11
|
dependencies:
|
12
12
|
- !ruby/object:Gem::Dependency
|
13
13
|
name: rb_sys
|
@@ -57,11 +57,13 @@ files:
|
|
57
57
|
- ext/itsi_scheduler/src/itsi_scheduler/io_waiter.rs
|
58
58
|
- ext/itsi_scheduler/src/itsi_scheduler/timer.rs
|
59
59
|
- ext/itsi_scheduler/src/lib.rs
|
60
|
+
- ext/itsi_server/Cargo.lock
|
60
61
|
- ext/itsi_server/Cargo.toml
|
61
62
|
- ext/itsi_server/extconf.rb
|
62
63
|
- ext/itsi_server/src/body_proxy/big_bytes.rs
|
63
64
|
- ext/itsi_server/src/body_proxy/itsi_body_proxy.rs
|
64
65
|
- ext/itsi_server/src/body_proxy/mod.rs
|
66
|
+
- ext/itsi_server/src/env.rs
|
65
67
|
- ext/itsi_server/src/lib.rs
|
66
68
|
- ext/itsi_server/src/request/itsi_request.rs
|
67
69
|
- ext/itsi_server/src/request/mod.rs
|
@@ -70,8 +72,6 @@ files:
|
|
70
72
|
- ext/itsi_server/src/server/bind.rs
|
71
73
|
- ext/itsi_server/src/server/bind_protocol.rs
|
72
74
|
- ext/itsi_server/src/server/io_stream.rs
|
73
|
-
- ext/itsi_server/src/server/itsi_ca/itsi_ca.crt
|
74
|
-
- ext/itsi_server/src/server/itsi_ca/itsi_ca.key
|
75
75
|
- ext/itsi_server/src/server/itsi_server.rs
|
76
76
|
- ext/itsi_server/src/server/lifecycle_event.rs
|
77
77
|
- ext/itsi_server/src/server/listener.rs
|
@@ -1,13 +0,0 @@
|
|
1
|
-
-----BEGIN CERTIFICATE-----
|
2
|
-
MIIB9TCCAZugAwIBAgIUMpQtAScU2Ow9c1Xy/0b/kS/BuwcwCgYIKoZIzj0EAwIw
|
3
|
-
UDELMAkGA1UEBhMCVVMxDTALBgNVBAgMBEl0c2kxDTALBgNVBAcMBEl0c2kxEDAO
|
4
|
-
BgNVBAoMB0l0c2kgQ0ExETAPBgNVBAMMCGl0c2kuZnlpMB4XDTI1MDMwMzIwMjg1
|
5
|
-
N1oXDTM1MDMwMTIwMjg1N1owUDELMAkGA1UEBhMCVVMxDTALBgNVBAgMBEl0c2kx
|
6
|
-
DTALBgNVBAcMBEl0c2kxEDAOBgNVBAoMB0l0c2kgQ0ExETAPBgNVBAMMCGl0c2ku
|
7
|
-
ZnlpMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEqGdC9Vi1r7ARvqSkPXkAgiV5
|
8
|
-
gn2MMTeEafagrWT7G1onSh/G+Qstxl61kfFNLOTiy6NSgAtKG+gfveCTo0Pcz6NT
|
9
|
-
MFEwHQYDVR0OBBYEFN7zzDodmiK2VAzLDydDvb6Er+U+MB8GA1UdIwQYMBaAFN7z
|
10
|
-
zDodmiK2VAzLDydDvb6Er+U+MA8GA1UdEwEB/wQFMAMBAf8wCgYIKoZIzj0EAwID
|
11
|
-
SAAwRQIhAP8q3PiwqTwCbRvYvvetxH39mAce1mfQMosb33ns228VAiBXdb+p9s0o
|
12
|
-
5ug5g9/MTvrIPI7GgolXCWZunkouy0LSrw==
|
13
|
-
-----END CERTIFICATE-----
|