Skip to content

Commit c2d5c62

Browse files
committed
Merge FEATURE-ci-tls-ca-fixture: Repair strict TLS fixture chains
2 parents f3e01f9 + 2fffdc9 commit c2d5c62

9 files changed

Lines changed: 339 additions & 48 deletions

File tree

‎.gitignore‎

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -177,3 +177,5 @@ __pycache__/
177177
!scripts/test-v3-platform-ci.py
178178
!test/integ/release_ci_test.py
179179
!test/integ/release_platform_bundle_test.py
180+
181+
!scripts/test-v3-tls-fixtures.py

‎Makefile.am‎

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -286,7 +286,7 @@ check-hygiene:
286286
# still runs the full test suite plus the header / example / lint gates, but
287287
# skips the source-only doc gates whose inputs it cannot satisfy. Release runners
288288
# may provide a verified specs/ tooling bundle; it is not a source checkout.
289-
check-local: check-v3-compiled-operations check-headers check-examples lint-warning-suppressions lint-littletest-skip-exit-code lint-fence-balance check-v3-native-linkage check-v3-stage-install
289+
check-local: check-v3-compiled-operations check-headers check-examples lint-warning-suppressions lint-littletest-skip-exit-code lint-fence-balance check-v3-native-linkage check-v3-stage-install $(V3_TLS_FIXTURE_CHECK)
290290
@if test -d "$(top_srcdir)/specs" && test -e "$(top_srcdir)/.git"; then \
291291
$(MAKE) $(AM_MAKEFLAGS) check-readme check-release-notes check-doxygen ; \
292292
else \
@@ -643,3 +643,15 @@ EXTRA_DIST += .github/workflows/codeql-analysis.yml .github/workflows/verify-bui
643643
EXTRA_DIST += docs/v3-ci.md
644644

645645
EXTRA_DIST += scripts/ci-macos-toolchain.sh scripts/test-v3-platform-ci.py
646+
647+
# Real independent certificate fixtures must satisfy strict client verification.
648+
EXTRA_DIST += scripts/test-v3-tls-fixtures.py
649+
if NATIVE_V3_TLS
650+
V3_TLS_FIXTURE_CHECK = check-v3-tls-fixtures
651+
check-v3-tls-fixtures:
652+
@CXX="$(CXX)" V3_TLS_CFLAGS="$(V3_TLS_CFLAGS)" V3_TLS_LIBS="$(V3_TLS_LIBS)" python3 $(top_srcdir)/scripts/test-v3-tls-fixtures.py
653+
else
654+
check-v3-tls-fixtures:
655+
@echo "check-v3-tls-fixtures: requires the TLS-on provider lane"
656+
endif
657+
.PHONY: check-v3-tls-fixtures

‎scripts/test-v3-tls-fixtures.py‎

Lines changed: 133 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,133 @@
1+
#!/usr/bin/env python3
2+
"""Real independent fixture generation and strict TLS handshakes; no server library."""
3+
import os
4+
from pathlib import Path
5+
import shlex
6+
import shutil
7+
import ssl
8+
import subprocess
9+
import tempfile
10+
import unittest
11+
12+
SOURCE = Path(__file__).resolve().parent.parent
13+
14+
15+
class TLSFixtures(unittest.TestCase):
16+
@classmethod
17+
def setUpClass(cls):
18+
cls.temporary = tempfile.TemporaryDirectory(prefix='v3-tls-fixtures-')
19+
cls.addClassCleanup(cls.temporary.cleanup)
20+
cls.root = Path(cls.temporary.name).resolve()
21+
cls.fixtures = cls.root / 'fixtures'
22+
shutil.copytree(SOURCE / 'test/data/tls_credentials', cls.fixtures)
23+
executable = cls.root / ('client.exe' if os.name == 'nt' else 'client')
24+
cls.executable = executable
25+
command = (shlex.split(os.environ.get('CXX', 'c++')) + ['-std=c++20']
26+
+ shlex.split(os.environ.get('V3_TLS_CFLAGS', ''))
27+
+ [str(SOURCE / 'test/installed/tls_client.cpp')]
28+
+ shlex.split(os.environ.get('V3_TLS_LIBS', '-lssl -lcrypto'))
29+
+ (['-lws2_32'] if os.name == 'nt' else []) + ['-o', str(executable)])
30+
subprocess.run(command, check=True, capture_output=True, text=True, timeout=60)
31+
subprocess.run([str(executable), 'fixtures', str(cls.fixtures)], check=True,
32+
capture_output=True, text=True, timeout=10)
33+
34+
def handshake(self, identity, host='a.example', trust='root', client=None):
35+
"""Exchange real TLS records in memory, including certificate/hostname verification."""
36+
chain = self.root / (identity + '-chain.pem')
37+
chain.write_bytes((self.fixtures / (identity + '.pem')).read_bytes()
38+
+ ((self.fixtures / 'intermediate.pem').read_bytes()
39+
if identity in ('a', 'b') else b''))
40+
server = ssl.SSLContext(ssl.PROTOCOL_TLS_SERVER)
41+
server.load_cert_chain(str(chain), str(self.fixtures / (identity + '-key.pem')))
42+
if client:
43+
server.load_verify_locations(str(self.fixtures / 'client-root.pem'))
44+
server.verify_mode = ssl.CERT_REQUIRED
45+
context = ssl.create_default_context(cafile=str(self.fixtures / (trust + '.pem')))
46+
# Python 3.14 enables this by default; exercise the same policy on older Python.
47+
context.verify_flags |= ssl.VERIFY_X509_STRICT
48+
self.assertTrue(context.check_hostname)
49+
self.assertEqual(ssl.CERT_REQUIRED, context.verify_mode)
50+
if client:
51+
client_chain = self.root / (client + '-chain.pem')
52+
client_chain.write_bytes((self.fixtures / (client + '.pem')).read_bytes()
53+
+ ((self.fixtures / 'client-intermediate.pem').read_bytes()
54+
if client != 'client-other' else b''))
55+
context.load_cert_chain(str(client_chain), str(self.fixtures / (client + '-key.pem')))
56+
inbound, outbound = [ssl.MemoryBIO(), ssl.MemoryBIO()], [ssl.MemoryBIO(), ssl.MemoryBIO()]
57+
peers = [context.wrap_bio(inbound[0], outbound[0], server_hostname=host),
58+
server.wrap_bio(inbound[1], outbound[1], server_side=True)]
59+
done = [False, False]
60+
for _ in range(20):
61+
for index, peer in enumerate(peers):
62+
if not done[index]:
63+
try:
64+
peer.do_handshake()
65+
done[index] = True
66+
except ssl.SSLWantReadError:
67+
pass
68+
inbound[1 - index].write(outbound[index].read())
69+
if all(done):
70+
peers[0].write(b'fixture-ok')
71+
inbound[1].write(outbound[0].read())
72+
self.assertEqual(b'fixture-ok', peers[1].read())
73+
return
74+
self.fail('TLS handshake did not complete within 20 record exchanges')
75+
76+
def test_static_sni_chains_pass_strict_handshake(self):
77+
for name in ('a', 'b'):
78+
with self.subTest(identity=name):
79+
self.handshake(name, name + '.example')
80+
81+
def test_rotation_chain_passes_strict_handshake(self):
82+
self.handshake('a-replacement')
83+
84+
def test_wrong_hostname_is_rejected(self):
85+
for name in ('a', 'b', 'a-replacement'):
86+
with self.subTest(identity=name):
87+
with self.assertRaises(ssl.SSLCertVerificationError) as error:
88+
self.handshake(name, 'wrong.example')
89+
self.assertEqual(62, error.exception.verify_code, str(error.exception))
90+
91+
def test_untrusted_chain_is_rejected(self):
92+
for name in ('a', 'b', 'a-replacement'):
93+
with self.subTest(identity=name):
94+
with self.assertRaises(ssl.SSLCertVerificationError) as error:
95+
self.handshake(name, name + '.example' if name == 'b' else 'a.example', 'client-root')
96+
self.assertIn(error.exception.verify_code, (20, 21), str(error.exception))
97+
98+
def test_server_fixture_regeneration_preserves_leaf_material(self):
99+
regenerated = self.root / 'regenerated'
100+
shutil.copytree(SOURCE / 'test/data/tls_credentials', regenerated)
101+
result = subprocess.run([str(self.executable), 'regenerate-server', str(regenerated)],
102+
capture_output=True, text=True, timeout=10)
103+
self.assertEqual(0, result.returncode, result.stdout + result.stderr)
104+
for path in (SOURCE / 'test/data/tls_credentials').iterdir():
105+
if path.name not in ('root.pem', 'intermediate.pem', 'a.pem', 'b.pem'):
106+
self.assertEqual(path.read_bytes(), (regenerated / path.name).read_bytes(), path.name)
107+
for name in ('root', 'intermediate', 'a', 'b'):
108+
flags = ['-noout', '-dates', '-serial', '-subject', '-issuer']
109+
if name in ('a', 'b'):
110+
flags.append('-pubkey')
111+
def metadata(directory):
112+
return subprocess.run([os.environ.get('OPENSSL', 'openssl'), 'x509',
113+
'-in', str(directory / (name + '.pem'))] + flags,
114+
check=True, capture_output=True, timeout=5).stdout
115+
self.assertEqual(metadata(SOURCE / 'test/data/tls_credentials'), metadata(regenerated))
116+
self.fixtures = regenerated
117+
self.handshake('a')
118+
self.handshake('b', 'b.example')
119+
120+
def test_valid_mtls_client_is_accepted(self):
121+
self.handshake('a', client='client-valid')
122+
123+
def test_negative_mtls_clients_are_rejected(self):
124+
for client in ('client-other', 'client-expired', 'client-wrong-purpose'):
125+
with self.subTest(client=client):
126+
with self.assertRaises(ssl.SSLCertVerificationError) as error:
127+
self.handshake('a', client=client)
128+
self.assertEqual({'client-other': 20, 'client-expired': 10,
129+
'client-wrong-purpose': 26}[client], error.exception.verify_code)
130+
131+
132+
if __name__ == '__main__':
133+
unittest.main()
Lines changed: 97 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,97 @@
1+
# FEATURE-ci-tls-ca-fixture: make independent TLS fixture chains strictly valid
2+
3+
Status: Complete (local) — validated and finalized into v3; native CI remains pending.
4+
Base: local `v3` at `f3e01f9dddc07c5aa94051b89220095d05c47f53`.
5+
Evidence: `.debug/ci-tls-ca-fixture-a1jpmct9/journal.md` and its `implementation/` logs.
6+
7+
The macOS checkout package job `120468733495` (run `39969683050`) fails
8+
`check-v3-public-tls.py` at its initial SNI loop, line 117, with certificate
9+
verification error 92: CA certificate lacks key usage. Those initial connections
10+
serve the copied static a/b certificates, through intermediate.pem and root.pem.
11+
Real strict verification reproduces missing key usage on both static CAs.
12+
The generated rotation chain has a separate instance of incomplete extensions:
13+
strict verification reports missing leaf authority key identifier (85), root
14+
key usage (92), and root subject key identifier (86). A rotation-only repair
15+
would leave the observed CI failure intact.
16+
17+
Action items:
18+
19+
- [x] Read the plan correction, literal journal and actual macOS traceback; isolate
20+
this worktree from the exact local v3 base and preserve other work.
21+
- [x] Observe RED with the real independent C++ fixture generator, copied static
22+
credentials, and real strict TLS handshakes, including error 92 on a/b and
23+
error 85 on rotation. Repeat the causal original/repaired comparison with
24+
the existing supported OpenSSL 3.5.9 verifier.
25+
- [x] Add critical certificate-signing/CRL-signing key usage to both static CAs;
26+
reissue their chain while preserving existing a/b leaf private/public keys,
27+
all subjects/issuer names, exact validity dates and serial numbers.
28+
- [x] Add rotation root key usage/SKI and leaf AKI before signing, without changing
29+
the intentionally malformed ACME generator variants or client verification policy.
30+
- [x] Retain reproducible server-fixture regeneration in the existing independent
31+
tool; preserve all mTLS credentials and private-key files byte-for-byte.
32+
- [x] Verify actual strict handshakes, hostname/untrusted-chain rejection, valid
33+
mTLS acceptance and rejection for expired, untrusted and wrong-purpose clients.
34+
- [x] Register/distribute the focused test in TLS-on check-local; run the configured
35+
standalone Make target, bootstrap, changed-file cpplint, syntax and diff checks.
36+
- [x] Caller: Groundwork validation and finalization into v3.
37+
- [ ] CI / v3 PR: native macOS/Python 3.14 checkout/archive public TLS matrices.
38+
39+
Implementation and reproduction:
40+
41+
`test/installed/tls_client.cpp` already generates the installed-consumer rotation
42+
and ACME fixtures independently of libhttpserver. Its `regenerate-server DIR`
43+
mode now regenerates only root.pem, intermediate.pem, a.pem and b.pem using the
44+
existing certificates as templates. For example, after compiling that tool
45+
against the selected provider, run `independent-client regenerate-server
46+
COPIED_FIXTURE_DIRECTORY`. Its existing `fixtures DIR` mode remains separate.
47+
Original CA private keys were never tracked; changing signed CA extensions
48+
requires replacement test CA keypairs and re-signing the server chain. New
49+
RSA-2048 CA keys are ephemeral and discarded. Regeneration reproduces the
50+
chain/metadata invariant with fresh random CA keys, rather than byte-identical
51+
certificates. All leaf keys, exact dates/serials and negative client credentials
52+
remain unchanged. Only CA public keys, related key identifiers and signatures
53+
change in the four static certificates, in addition to the new CA key usages.
54+
55+
OpenSSL's [strict verification documentation](https://docs.openssl.org/3.5/man1/openssl-verification-options/)
56+
requires key usage for CAs, SKI for version 3 CAs, and AKI for non-self-signed
57+
version 3 certificates. The repair satisfies those chain checks without changing
58+
runtime code, security policy, provider selection, dependencies or CI pins.
59+
60+
Local verification and limits:
61+
62+
- Seven focused tests pass through `make check-v3-tls-fixtures` in a real
63+
TLS-on configured build using existing OpenSSL 3.5.9 headers/static libraries.
64+
The test compiles/runs the actual independent C++ fixture tool, exchanges real
65+
TLS records through Python SSL MemoryBIOs, and sends application data after
66+
successful handshakes. Python 3.12.3 uses system OpenSSL 3.0.13 with
67+
`VERIFY_X509_STRICT` enabled explicitly to match the newer Python client policy.
68+
- The supported OpenSSL 3.5.9 executable separately verifies original a/b chains
69+
fail with error 92 and original rotation fails with 85/92/86. Repaired a/b and
70+
rotation pass `verify -x509_strict -purpose sslserver -verify_hostname ...`.
71+
This executable links the existing provider statically; changing LD_LIBRARY_PATH
72+
does not replace Python's separate system OpenSSL engine.
73+
- Wrong host fails specifically with verification code 62; untrusted server
74+
chains fail with 20/21. mTLS negatives fail specifically with 20 (untrusted),
75+
10 (expired), and 26 (wrong purpose); the valid client completes a handshake.
76+
- Original/repaired actual generator outputs retain identical extension content
77+
for all twelve ACME variants. The ACME generator loop is unchanged, including
78+
expired/future date logic. No malformed-certificate acceptance is claimed.
79+
- A regeneration test checks certificate dates/serials/subjects/issuer names,
80+
unchanged leaf public keys, every untouched file byte-for-byte, and strict a/b
81+
handshakes from a fresh regeneration. A retained pre-change hash/metadata
82+
manifest also confirms original leaf keys and mTLS fixture bytes were preserved.
83+
- `./bootstrap`, real TLS-on configure, configured focused Make target,
84+
`cpplint test/installed/tls_client.cpp`, Python AST syntax and `git diff --check`
85+
pass. Bootstrap retains existing aminclude.am .PHONY / AC_OUTPUT warnings;
86+
the C++ compiler retains the existing fclose-deleter attribute warning in the
87+
pre-existing fixture function. No new build warning was introduced.
88+
- No broad baseline, full public TLS matrix or nonlocal platform run was started.
89+
Native macOS/Python 3.14 execution remains CI / v3-PR owned, not passed locally.
90+
Prior Darwin deadline, old Linux parity and concurrent Windows install
91+
uncertainties remain closed to further diagnosis under this task.
92+
93+
Groundwork validation passed at `77b1113fcb1dabc64c1563852764cfae08571e6a`
94+
(1 iteration, 0 fixed, 0 unworked; four approvals). Final seven-test Make evidence
95+
binds committed inputs by execution chronology and executor attestation; the older
96+
supplemental provider log is not claimed as a final Python gate. Native checks
97+
remain incomplete and CI/v3-PR owned.

‎test/data/tls_credentials/a.pem‎

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -9,11 +9,11 @@ bUIxXkyyzbdO0Pn93KgnhS6Tpjb5VSpFNQ1+KnafYUmKFjCvVT3fc2PY6aejHfnJ
99
dIHpe3ElECpbeuPwhDjx5hDTgQxXduNOkOawWAJ6nFRTN3EBdP59hLdOBHiH3ppx
1010
zm+hwRRj9xg7Tuq10/9jWimoWNrQxi8CAwEAAaNmMGQwDAYDVR0TAQH/BAIwADAU
1111
BgNVHREEDTALgglhLmV4YW1wbGUwHQYDVR0OBBYEFKg1sciHv3NIB1tfkf/7jvSD
12-
dIAFMB8GA1UdIwQYMBaAFE0G6u9Yw3UtnBzPEldmxzXksmADMA0GCSqGSIb3DQEB
13-
CwUAA4IBAQAiEOqnKdliz2YcUOYMcm9XsnnohsUOZRKlIsvO/KZxfo0tSNImhP4l
14-
bIJG2MccbJsAuQwvphYMI79ilBfN59w+XIUk1KinPu7yvlnsUeWqT67Iln0AjOi1
15-
qU9A+oE5hLN9yHDs+IhRBW0Zg9RB5lgnRg8ozc6mOKVGVbb86w+6yTGlrRk5Q3cm
16-
QdVlaDq2s3T1FtN+KZs17L8wFN9up516umGrlYKEoaNJtLC7JfoAhmUq2Bkn/gbG
17-
HlpV09U0ov03Y+CJhqSCjSUmlWv91Lu4I0M4jUfUXLCeJFW7ZHeVrfDoOhWXE/Ga
18-
YvmRFgHUPB8oAuSQJe5ewD2PZtprHdIq
12+
dIAFMB8GA1UdIwQYMBaAFPf1/w0eMonXp2q/iEQdf7WVs0M9MA0GCSqGSIb3DQEB
13+
CwUAA4IBAQB9pUEnG+LCia4ZOWgQHxG4NvuTMSbaIzgwLUkrVsUTk4ivH4V2bacf
14+
o5+KTN8sZhKL8PHzDisZF5HSqq8mZ4j9tMiMAKI3Sg6jVNZ4/G4CQIXvPPkcpTdW
15+
2Q/b6dHt4dD5i0zslGMIRI6EwNpyvnWgNBrRnMkbD6Hs+qr94qp9rUa7uPk6oZaW
16+
yo95k5PLiERVPuTTywq5rPjq0RISKHLfS4NIIbecE2ssrSLLiLlGighCWUngGcQv
17+
ZsaO2akhopIi8dG7d50FssrgBDP9Bku/kHgeBJfzIH3Pp7rOAAe+n75y4zVxZuBl
18+
S2WmcaBebP6q+E1kdFx/A0uDmBi15pTl
1919
-----END CERTIFICATE-----

‎test/data/tls_credentials/b.pem‎

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -9,11 +9,11 @@ w0frIAxv+WbXGu/e1s0JeqTljX/m79EBCXgkYiMM3eVJ6y86rQbREhM0gHNIrUwh
99
CtLYfMU4zOPPZMVf9H3rOccS39+mNxqmJDYNPf0hDeXHOalnHbVaK6dfwRS0LXA5
1010
5zLaX0mB6XuMgo+1/Jxhke88flK0geEbAgMBAAGjZjBkMAwGA1UdEwEB/wQCMAAw
1111
FAYDVR0RBA0wC4IJYi5leGFtcGxlMB0GA1UdDgQWBBScLyV/fQ3KcecQZwYUBOCN
12-
PjAWAjAfBgNVHSMEGDAWgBRNBurvWMN1LZwczxJXZsc15LJgAzANBgkqhkiG9w0B
13-
AQsFAAOCAQEAHinMtyou4s4ZFM+hcaZhOms1M8t2/a1ov0QgkKJ2m2l6IKOtcs1d
14-
0hGACH6cPW2tbcuTznHY1lr0/FcIZgnVDbx3IiEHCN0bxIdezeVBUIUHMrqtJ/dD
15-
+IZbimbjqQOSFyvezwJovHtuXVVbtZA02XZApgGGafevN8GLv4dIPmDH+262AHcj
16-
GSD1z39ZgsLBhp09PaZzxMqofzstn1iLIpfn5zyBTCGV2u5zLqrkcdxKuTVnnk7+
17-
duXYSiOqj98RL/zsQYr/vGZ2cCnrve6pu4XEXZJb06zQj7W+TynkSRMiGNQ419Px
18-
UNANmi8PoJHPd8S+sIHqhNZ19wkiQ9XNRQ==
12+
PjAWAjAfBgNVHSMEGDAWgBT39f8NHjKJ16dqv4hEHX+1lbNDPTANBgkqhkiG9w0B
13+
AQsFAAOCAQEANe/8gnVYlb19gl62iVsYlweCdvE38XMCaYxEjl+zC7oUbQDzykKR
14+
VVZpSfwEISoR7ZGyhDn57K/w3GTPF+xVL9yFeXwXFgEDD+9e4V8IaODkqLQ0BZYH
15+
+rHJeG+Xmea4Q+X7XFTG11HsKvHYW/tP3bNAq0Rl+bmwFlNqzTDgjd2zI71+TkRk
16+
G3t3Fyo9Tc+ef+eJEs0CrZw2mU4lUIKpfh0/ad/rrjClWOuWG8j1Z4ADfzEJsXCt
17+
+ky3oNQN7pETwT1Ln7OSPXPZjHAITqTCNW2Bdv1Wedv4Hgwxg0g3ew3M4TzbRN1L
18+
lugJPKpq2OmXOFozVXoY4vlIrog3owq8Qw==
1919
-----END CERTIFICATE-----
Lines changed: 15 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,19 @@
11
-----BEGIN CERTIFICATE-----
2-
MIIDBDCCAeygAwIBAgIBAjANBgkqhkiG9w0BAQsFADAXMRUwEwYDVQQDDAxyb290
2+
MIIDFDCCAfygAwIBAgIBAjANBgkqhkiG9w0BAQsFADAXMRUwEwYDVQQDDAxyb290
33
LmV4YW1wbGUwHhcNMjYxMDA3MDUxMDA4WhcNMzYxMDA0MDUxMDA4WjAfMR0wGwYD
44
VQQDDBRpbnRlcm1lZGlhdGUuZXhhbXBsZTCCASIwDQYJKoZIhvcNAQEBBQADggEP
5-
ADCCAQoCggEBAKZZmt25QAcWUGBVRmBc9SkDlRz//ZrbO30pGvTDkumhPwE5PJHn
6-
15x4e8k0VPRsgSs00XRv8nlLmv+ydBACtL8rMkPCKvKIuU0VZZ3qwbqZU8emLBME
7-
6x7Qb0U8Ce9vGxTaNJbmfze5Jq1RgOLusoyNp+Fp4lVIOiQqXvbxTGQA5WkY5U9Y
8-
S3sbMUj4xFxxtTHL6DgJw+gf1q+ldXapcF0kTDVyyhELD//l1C9nRrAOHxgx4ro6
9-
oV3A+CxxTaPppi+315it573WFDKiCd//zbF6WXwMRyiVhSOx9s1LhW5wnTkEObGn
10-
UjiQDxhrC4k3rTdme2nIptMuypdyvRSZRjkCAwEAAaNTMFEwDwYDVR0TAQH/BAUw
11-
AwEB/zAdBgNVHQ4EFgQUTQbq71jDdS2cHM8SV2bHNeSyYAMwHwYDVR0jBBgwFoAU
12-
n4aDyofdEPe7qMb1Eg7QfGoVYCAwDQYJKoZIhvcNAQELBQADggEBAENTYRi/EHo3
13-
s6A1p1C5R7fU4WvsKWry/GqTiQMLl/ulOUN3A4KWKMjNIjOz/pZjkPTEaMs0Pzsz
14-
mKawArvV9G9V4q7Tlevsea0iZ+/ks0DDWr9NS8q5CAvg13L3MFxo4YcaCM0wXhDN
15-
SN1r4MFYsRcFdSH5xPVTNlhuGOqoB7RLRPyLu1EyRS2rhETldDWFVxCLMLpZuNpV
16-
Ml+w4NHgGBZvTti+1eS+ZPwgZjUU/PIbDP1a81fahE0epy3A++/RUSX9+KItXko9
17-
O/nN667BxetBv5Rjb6Hn3dukw8VHVCbGb/V239N29yqNp4GMEOatXv9aCBZTVdPV
18-
9NcjUMRLzms=
5+
ADCCAQoCggEBAKddRzliAfAlEdLrXGwoDHbp5n4P8bHkb6quhVCAo35nwQ8tkxTv
6+
k1HXGyHEELbY9jly/LAcBfiL+CipbgBtytysIjaBLgVIxaQr4LBRwJ32FZ+K1fHu
7+
bJ5KOtXsRxoMcYLxkniRYwVB/qFBp3iLJyXHNnKbwOgYnNpSsjGvIqgdC0pKocLv
8+
60YIiW1VQwsgKJX+vLYQ3+YOIHHhXTXYl93ojwVGosVE5tGFFwfxCF7LE7s3K5w/
9+
A8frWXsfvZzsXipOXcUauIUvmPyMn7FDaoWRIJ8yvlfVFitpOu4/SDk8ZHFORjIA
10+
9SxyPTWlTm8GiiZkQ7WYMnDAkO8MGqjkkv0CAwEAAaNjMGEwDwYDVR0TAQH/BAUw
11+
AwEB/zAdBgNVHQ4EFgQU9/X/DR4yidenar+IRB1/tZWzQz0wDgYDVR0PAQH/BAQD
12+
AgEGMB8GA1UdIwQYMBaAFPlQxJE+KraVWm+KrVoJK8RibV1wMA0GCSqGSIb3DQEB
13+
CwUAA4IBAQAcx+7nMeT8hPZfm7CtcbGQLHJAI4LPIZE3Tcmf5Q0kE7S4lM5uWPge
14+
/rWDFG5sR4w2KL7SZgm5pR2rD/qXIYvTTCOgUpmpaLIFoUEO+X9hnKDtrJl7dep3
15+
BDe5P6L8ol7Qr38uOP9vOkVdKwHcmHOZr9aNHasTL9vOUy5jnQmTm6u9LKi5mokF
16+
w13QB/sa4tpiAMbj6mKqkAHl9WIlhZ/6G/c4IDMs8kzCtszpdNvcj1dw4QSPgg2L
17+
IIpPoWHmD3YYNJts0F9Ytc1E2fkR/J6GurhugtVT6J6DZLG9oftb254l9RGsYsS0
18+
XqIHNbeNF5e6SuN7DpH1IJFNr1g9/zVd
1919
-----END CERTIFICATE-----

0 commit comments

Comments
 (0)